0


《知识点扫盲 · 学会 WebService》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,如需交流,欢迎留言评论。👍

文章目录

在这里插入图片描述

写在前面的话

博主所在公司是医疗信息化厂商,同时拥有集成平台产品线,在针对跨不同厂商的系统,通常采用

WebService

进行数据交互。
本篇文章介绍一下

WebService

的实际应用,希望可以帮助到大家。

Tips:金鳞岂是池中物,一遇风云便化龙。


WebService 统括

技术简介

WebService 是一种基于网络的、分布式的计算技术,它允许不同的应用程序通过网络进行交互。WebService 使用标准的网络协议,如HTTP或HTTPS,以及基于XML的消息传递系统来交换数据。这种技术的主要目的是实现不同平台、不同语言编写的应用程序之间的互操作性。
WebService 的核心组件包括:

  1. SOAP(Simple Object Access Protocol):一种基于XML的消息传递协议,用于在网络上传输数据;
  2. WSDL(Web Services Description Language):一种XML格式,用于描述WebService的接口,包括可调用的操作、输入输出参数等信息;
  3. UDDI(Universal Description, Discovery, and Integration):一个用于发布和发现WebService的目录;

WebService 的主要优点是跨平台和语言无关性,使得不同系统之间的集成变得更为容易。

Tips:上面的概念大部分人看起来可能一头雾水,可能要说,直接介绍怎么用就好了,干嘛说这些有用的废话。其实也是为了保证内容的连贯性,下面来一个容易看懂的版本。

通俗来说,WebService 可以理解为一种特殊的 Http 调用方式,采用 XML格式作为出入参,下文简称“WS”。


常用实现

前面介绍 WS 的概念,可能有的人不熟悉,但接下来介绍的 Apache CXF,应该很多人见过。
Apache CXF 是 Apache 软件基金会的一个开源项目,支持 SOAP 和 RESTful 风格的Web服务。CXF提供了全面的功能,包括服务端的部署、客户端的生成以及数据绑定的支持。
无独有偶,同样的实现方案,还有 Apache Axis2、Spring-WS 等等很多种,这个很好理解,他们和 WS 的关系,就像HttpURLConnection、HttpClient、OkHttp 等技术都可以用于在Java应用程序中发送HTTP请求和接收HTTP响应。
Axis2,同样也是Apache的一个开源项目,是Axis的后续版本,支持SOAP和RESTful Web服务。Axis2提供了模块化的架构,易于扩展和定制。
Spring-WS,是Spring框架的一部分,专注于简化Web服务的开发。它支持SOAP协议,并且与Spring框架的其他部分紧密集成,提供了声明式的事务管理和安全性支持。
三者各有优缺点,这边也不去讨论优劣势,以最常见的

CXF

展开介绍实战运用。


SB 整合 CXF

Step1、引入 Maven 依赖,整合第一步基本是这个

<!-- CXF Starter --><dependency><groupId>org.apache.cxf</groupId><artifactId>cxf-spring-boot-starter-jaxws</artifactId><version>3.4.1</version><!-- 请使用最新的兼容版本 --></dependency>

Step2、创建服务端接口和实现类,正常些业务逻辑

@WebServicepublicinterfaceHelloWorldService{@WebMethodStringsayHello(@WebParam(name ="theName")String name);}@Service@WebService(endpointInterface ="com.lw.boot.ws.HelloWorldService")publicclassHelloWorldServiceImplimplementsHelloWorldService{@OverridepublicStringsayHello(String name){return"Hello, "+ name +"!";}}

Step3、添加CXF服务端配置类,这里主要是创建服务端

@ConfigurationpublicclassCxfConfig{privatefinalBus bus;privatefinalHelloWorldService helloWorldService;publicCxfConfig(Bus bus,HelloWorldService helloWorldService){this.bus = bus;this.helloWorldService = helloWorldService;}@BeanpublicEndpointendpoint(){EndpointImpl endpoint =newEndpointImpl(bus, helloWorldService);
        endpoint.publish("/hello");return endpoint;}}

Step4、模拟实现客户端调用

@RestControllerpublicclassHelloWsController{@GetMapping("/wsTest")publicStringtest(@RequestParamString name){JaxWsProxyFactoryBean factory =newJaxWsProxyFactoryBean();
        factory.setServiceClass(HelloWorldService.class);
        factory.setAddress("http://localhost:8082/services/hello");HelloWorldService helloWorldService =(HelloWorldService) factory.create();return helloWorldService.sayHello(name);}}

Step5、运行测试
运行 Spring Boot 应用程序,访问以下 URL 来测试服务端和客户端:
服务端 WSDL 地址:http://localhost:8082/services/hello?wsdl
客户端测试 URL:http://localhost:8082/wsTest?name=cjwmy

Tips:路径services是默认值,可以通过

cxf.path

设定。


实战拓展

【WSDL与效果测试】
访问前面示例:http://localhost:8080/services/hello?wsdl
内容效果如下图,关键信息已经圈出来,其实就是描述这个WS服务端的一个能力,hello下面可以有多个方法。
image.png
进一步用测试工具运行,效果如下:
image.png
到此,还是挺简单而且顺利的。

【关于CXF客户端】
实际开发中,CXF客户端的封装远不止上面示例那么简单,它应该和 HttpUtil 一样的重要级别,客户端初始化策略、请求超时、请求重试、熔断限流、返回值包装、入参多样化、链路日志记录等元素,一个都不能缺少,由于本篇不是

框架封装系列

,那就不展开介绍了,后面专栏展开。
要特别提醒的是,由于创建CXF客户端是一个耗时的动作,可以考虑如何针对同样URL的客户端的复用,同时首次访问慢的问题也应该要解决。
还是给一段示例代码:

@RequiredArgsConstructorpublicclassWSRequestUtil{privatestaticfinalLogger logger =LoggerFactory.getLogger(WSRequestUtil.class);privatestaticfinalMap<String,Client>WS_CLIENT_CACHE_MAP=newConcurrentHashMap<>();privatestaticfinalMap<String,AtomicInteger>WS_CLIENT_COUNT_MAP=newConcurrentHashMap<>();/**
     * 发送WebService的请求后等待响应的时间,超过设置的时长就认为是响应超时.以毫秒为单位,默认是60000毫秒,即60秒.
     */privatestaticfinalintRECEIVE_TIMEOUT=60000;/**
     * WebService以TCP连接为基础,这个属性可以理解为TCP握手时的时间设置,超过设置的时间就认为是连接超时.以毫秒为单位,默认是30000毫秒,即30秒。
     */privatestaticfinalintCONNECTION_TIMEOUT=30000;/**
     * 当前请求数
     */publicstaticfinalAtomicIntegerCURRENT_TASK_COUNT=newAtomicInteger();publicstaticfinalintWS_REQUEST_MAX_WAIT=50;privatefinalOnelinkBizProperties.BizParam bizParam;privatefinalTracer tracer;publicstaticWSRequestUtilSELF;@PostConstructpublicvoidinit(){SELF=this;}/**
     * 获取客户端
     *
     * @param wsUrl          ws地址
     * @param receiveTimeout 响应超时时间
     * @return Client 客户端对象
     * @throws Exception 异常信息
     */privatestaticClientgetClient(String wsUrl,int receiveTimeout)throwsException{returngetClient(wsUrl,CONNECTION_TIMEOUT, receiveTimeout);}/**
     * 获取客户端
     *
     * @param wsUrl ws地址
     * @return Client 客户端对象
     * @throws Exception 异常信息
     */privatestaticClientgetClient(String wsUrl,int connectionTimeout,int receiveTimeout)throwsException{if(ValidUtil.isEmptyOrNull(wsUrl)){returnnull;}if(!WS_CLIENT_CACHE_MAP.containsKey(wsUrl)){AtomicInteger clientVisitCounter =WS_CLIENT_COUNT_MAP.computeIfAbsent(wsUrl, url ->newAtomicInteger(0));if(clientVisitCounter.incrementAndGet()>WS_REQUEST_MAX_WAIT){
                clientVisitCounter.decrementAndGet();
                logger.warn("WS客户端创建过于频繁【{}】:{}",WS_REQUEST_MAX_WAIT, wsUrl);throwApiException.createEx(ExceptionCodeEnum.WS_CLIENT_CREATE_RATE_LIMIT_ERROR,WS_REQUEST_MAX_WAIT);}}Span span =SELF.tracer.nextSpan().start();if(span !=null){
            span.name("[WS] [CreateClient] "+ wsUrl);
            span.tag(TraceSpanConstant.WS_URL, wsUrl);}returnWS_CLIENT_CACHE_MAP.computeIfAbsent(wsUrl, key ->{try{Client client =OnelinkThreadUtil.submit(()->createWsClient(wsUrl, connectionTimeout, receiveTimeout)).get(30,TimeUnit.SECONDS);AtomicInteger wsClientCounter =WS_CLIENT_COUNT_MAP.get(wsUrl);if(client !=null&& wsClientCounter !=null){
                    wsClientCounter.set(0);}return client;}catch(TimeoutException e){decrementCounter(wsUrl);if(span !=null){
                    span.error(e);}
                logger.error("获取WS客户端超时,wsUrl:{}", wsUrl);throwApiException.createEx(e,ExceptionCodeEnum.WS_CLIENT_CREATE_ERROR,"WS Client Timeout");}catch(Exception e){decrementCounter(wsUrl);if(span !=null){
                    span.error(e);}
                logger.error("获取WS客户端失败,wsUrl:{}", wsUrl);throwApiException.createEx(e,ExceptionCodeEnum.WS_CLIENT_CREATE_ERROR, e.getMessage());}finally{OnelinkTracerUtil.finishSpan(span);}});}privatestaticvoiddecrementCounter(String wsUrl){AtomicInteger wsClientCounter =WS_CLIENT_COUNT_MAP.get(wsUrl);// 客户端创建成功重置计数器if(wsClientCounter !=null){
            wsClientCounter.decrementAndGet();}}privatestaticClientcreateWsClient(String wsUrl,int connectionTimeout,int receiveTimeout){long start =System.currentTimeMillis();
        logger.info("CXF调用webservice生成动态客户端");OnelinkDynamicClientFactory factory =OnelinkDynamicClientFactory.newInstance();Client cachedClient = factory.createClient(wsUrl);
        logger.info("CXF调用webservice生成动态客户端成功,耗时: {}",System.currentTimeMillis()- start);//设置超时时间HTTPConduit http =(HTTPConduit) cachedClient.getConduit();HTTPClientPolicy httpClientPolicy =newHTTPClientPolicy();
        httpClientPolicy.setConnectionTimeout(connectionTimeout);
        httpClientPolicy.setAllowChunking(false);
        httpClientPolicy.setReceiveTimeout(receiveTimeout);
        http.setClient(httpClientPolicy);return cachedClient;}/**
     * 执行请求
     *
     * @param wsUrl      url地址
     * @param methodName 方法名称
     * @param params     参数集
     * @return String 返回信息
     */publicstaticObject[]doRequest(String wsUrl,String methodName,Object... params)throwsException{returndoRequest(wsUrl, methodName,RECEIVE_TIMEOUT, params);}/**
     * 执行请求
     *
     * @param wsUrl             url地址
     * @param methodName        方法名称
     * @param connectionTimeout 连接超时时间,单位:毫秒
     * @param receiveTimeout    接受超时时间,单位:毫秒
     * @param params            参数集
     * @return String 返回信息
     */publicstaticObject[]doRequest(String wsUrl,String methodName,int connectionTimeout,int receiveTimeout,Object... params)throwsException{
        logger.debug("请求地址:{},请求方法:{}, 请求参数{}", wsUrl, methodName, params);if(ValidUtil.isEmptyOrNull(wsUrl)){thrownewIllegalArgumentException("接口地址不能为空");}if(ValidUtil.isEmptyOrNull(methodName)){thrownewIllegalArgumentException("方法名不能为空");}if(CURRENT_TASK_COUNT.incrementAndGet()>SELF.bizParam.getWsMaxRequestCount()&&ServerStatusUtil.isHttpMainThreadPool()){String msg =StrUtil.format("第三方请求过于频繁【{}】,请稍后再试!",SELF.bizParam.getWsMaxRequestCount());thrownewRuntimeException(msg);}try{Client client =getClient(wsUrl, connectionTimeout, receiveTimeout);OnelinkI18nAssert.notNull(client,ExceptionCodeEnum.VALUE_NOT_NULL,"WebServiceClient");returninvokeAndTrace(wsUrl, methodName, params, client);}catch(Exception e){String stackTraceStr =ExceptionUtil.stacktraceToString(e);
            logger.error("WS请求失败, 请求地址:{}, 请求方法:{}, 请求参数{}, 异常信息:{}", wsUrl, methodName, params, stackTraceStr);if(e instanceofApiException){throw e;}throwApiException.createEx(ExceptionCodeEnum.WS_OUTER_ERROR, e.getMessage());}finally{CURRENT_TASK_COUNT.decrementAndGet();}}privatestaticObject[]invokeAndTrace(String wsUrl,String methodName,Object[] params,Client client)throwsException{Span span =SELF.tracer.nextSpan();if(span !=null){
            span.name(StrUtil.format("[WS] {}", methodName));
            span.tag(TraceSpanConstant.WS_URL, wsUrl);
            span.tag(TraceSpanConstant.WS_METHOD_NAME, methodName);
            span.tag(TraceSpanConstant.WS_PARAM,JSON.toJSONString(params));
            span.start();}try{return client.invoke(methodName, params);}catch(Throwable e){if(span !=null){
                span.error(e);}throw e;}finally{OnelinkTracerUtil.finishSpan(span);}}/**
     * 执行请求
     *
     * @param wsUrl      url地址
     * @param methodName 方法名称
     * @param params     参数集
     * @return String 返回信息
     */publicstaticObject[]doRequest(String wsUrl,String methodName,int receiveTimeout,Object... params)throwsException{returndoRequest(wsUrl, methodName,CONNECTION_TIMEOUT, receiveTimeout, params);}}

总结陈词

上文介绍了

WebService

的基础用法,仅供参考,希望可以帮助到大家。
💗 后续会更新企业常用技术栈的若干系列文章,敬请期待。

在这里插入图片描述

标签: java webservice spring

本文转载自: https://blog.csdn.net/syb513812/article/details/140407931
版权归原作者 战神刘玉栋 所有, 如有侵权,请联系我们删除。

“《知识点扫盲 · 学会 WebService》”的评论:

还没有评论