• 《知识点扫盲 · 学会 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 依赖,整合第一步基本是这个

    
    <dependency>
        <groupId>org.apache.cxfgroupId>
        <artifactId>cxf-spring-boot-starter-jaxwsartifactId>
        <version>3.4.1version> 
    dependency>
    

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

    @WebService
    public interface HelloWorldService {
    
        @WebMethod
        String sayHello(@WebParam(name = "theName") String name);
    }
    
    @Service
    @WebService(endpointInterface = "com.lw.boot.ws.HelloWorldService")
    public class HelloWorldServiceImpl implements HelloWorldService {
    
        @Override
        public String sayHello(String name) {
            return "Hello, " + name + "!";
        }
    }
    

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

    @Configuration
    public class CxfConfig {
    
        private final Bus bus;
        private final HelloWorldService helloWorldService;
    
        public CxfConfig(Bus bus, HelloWorldService helloWorldService) {
            this.bus = bus;
            this.helloWorldService = helloWorldService;
        }
    
        @Bean
        public Endpoint endpoint() {
            EndpointImpl endpoint = new EndpointImpl(bus, helloWorldService);
            endpoint.publish("/hello");
            return endpoint;
        }
    }
    

    Step4、模拟实现客户端调用

    @RestController
    public class HelloWsController {
    
        @GetMapping("/wsTest")
        public String test(@RequestParam String name) {
            JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
            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的客户端的复用,同时首次访问慢的问题也应该要解决。
    还是给一段示例代码:

    @RequiredArgsConstructor
    public class WSRequestUtil {
    
        private static final Logger logger = LoggerFactory.getLogger(WSRequestUtil.class);
    
        private static final Map<String, Client> WS_CLIENT_CACHE_MAP = new ConcurrentHashMap<>();
    
        private static final Map<String, AtomicInteger> WS_CLIENT_COUNT_MAP = new ConcurrentHashMap<>();
    
        /**
         * 发送WebService的请求后等待响应的时间,超过设置的时长就认为是响应超时.以毫秒为单位,默认是60000毫秒,即60秒.
         */
        private static final int RECEIVE_TIMEOUT = 60000;
    
        /**
         * WebService以TCP连接为基础,这个属性可以理解为TCP握手时的时间设置,超过设置的时间就认为是连接超时.以毫秒为单位,默认是30000毫秒,即30秒。
         */
        private static final int CONNECTION_TIMEOUT = 30000;
    
        /**
         * 当前请求数
         */
        public static final AtomicInteger CURRENT_TASK_COUNT = new AtomicInteger();
        public static final int WS_REQUEST_MAX_WAIT = 50;
    
        private final OnelinkBizProperties.BizParam bizParam;
    
        private final Tracer tracer;
    
        public static WSRequestUtil SELF;
    
    
        @PostConstruct
        public void init() {
            SELF = this;
        }
    
        /**
         * 获取客户端
         *
         * @param wsUrl          ws地址
         * @param receiveTimeout 响应超时时间
         * @return Client 客户端对象
         * @throws Exception 异常信息
         */
        private static Client getClient(String wsUrl, int receiveTimeout) throws Exception {
            return getClient(wsUrl, CONNECTION_TIMEOUT, receiveTimeout);
        }
    
    
        /**
         * 获取客户端
         *
         * @param wsUrl ws地址
         * @return Client 客户端对象
         * @throws Exception 异常信息
         */
        private static Client getClient(String wsUrl, int connectionTimeout, int receiveTimeout) throws Exception {
            if (ValidUtil.isEmptyOrNull(wsUrl)) {
                return null;
            }
            if (!WS_CLIENT_CACHE_MAP.containsKey(wsUrl)) {
                AtomicInteger clientVisitCounter = WS_CLIENT_COUNT_MAP.computeIfAbsent(wsUrl, url -> new AtomicInteger(0));
                if (clientVisitCounter.incrementAndGet() > WS_REQUEST_MAX_WAIT) {
                    clientVisitCounter.decrementAndGet();
                    logger.warn("WS客户端创建过于频繁【{}】:{}", WS_REQUEST_MAX_WAIT, wsUrl);
                    throw ApiException.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);
            }
            return WS_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);
                    throw ApiException.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);
                    throw ApiException.createEx(e, ExceptionCodeEnum.WS_CLIENT_CREATE_ERROR, e.getMessage());
                } finally {
                    OnelinkTracerUtil.finishSpan(span);
                }
            });
        }
    
        private static void decrementCounter(String wsUrl) {
            AtomicInteger wsClientCounter = WS_CLIENT_COUNT_MAP.get(wsUrl);
            // 客户端创建成功重置计数器
            if (wsClientCounter != null) {
                wsClientCounter.decrementAndGet();
            }
        }
    
        private static Client createWsClient(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 = new HTTPClientPolicy();
            httpClientPolicy.setConnectionTimeout(connectionTimeout);
            httpClientPolicy.setAllowChunking(false);
            httpClientPolicy.setReceiveTimeout(receiveTimeout);
            http.setClient(httpClientPolicy);
            return cachedClient;
        }
    
        /**
         * 执行请求
         *
         * @param wsUrl      url地址
         * @param methodName 方法名称
         * @param params     参数集
         * @return String 返回信息
         */
        public static Object[] doRequest(String wsUrl, String methodName, Object... params) throws Exception {
            return doRequest(wsUrl, methodName, RECEIVE_TIMEOUT, params);
        }
    
        /**
         * 执行请求
         *
         * @param wsUrl             url地址
         * @param methodName        方法名称
         * @param connectionTimeout 连接超时时间,单位:毫秒
         * @param receiveTimeout    接受超时时间,单位:毫秒
         * @param params            参数集
         * @return String 返回信息
         */
        public static Object[] doRequest(String wsUrl, String methodName, int connectionTimeout, int receiveTimeout, Object... params) throws Exception {
            logger.debug("请求地址:{},请求方法:{}, 请求参数{}", wsUrl, methodName, params);
            if (ValidUtil.isEmptyOrNull(wsUrl)) {
                throw new IllegalArgumentException("接口地址不能为空");
            }
            if (ValidUtil.isEmptyOrNull(methodName)) {
                throw new IllegalArgumentException("方法名不能为空");
            }
            if (CURRENT_TASK_COUNT.incrementAndGet() > SELF.bizParam.getWsMaxRequestCount()
                    && ServerStatusUtil.isHttpMainThreadPool()) {
                String msg = StrUtil.format("第三方请求过于频繁【{}】,请稍后再试!", SELF.bizParam.getWsMaxRequestCount());
                throw new RuntimeException(msg);
            }
            try {
                Client client = getClient(wsUrl, connectionTimeout, receiveTimeout);
                OnelinkI18nAssert.notNull(client, ExceptionCodeEnum.VALUE_NOT_NULL, "WebServiceClient");
                return invokeAndTrace(wsUrl, methodName, params, client);
            } catch (Exception e) {
                String stackTraceStr = ExceptionUtil.stacktraceToString(e);
                logger.error("WS请求失败, 请求地址:{}, 请求方法:{}, 请求参数{}, 异常信息:{}", wsUrl, methodName, params, stackTraceStr);
                if (e instanceof ApiException) {
                    throw e;
                }
                throw ApiException.createEx(ExceptionCodeEnum.WS_OUTER_ERROR, e.getMessage());
            } finally {
                CURRENT_TASK_COUNT.decrementAndGet();
            }
        }
    
        private static Object[] invokeAndTrace(String wsUrl, String methodName, Object[] params, Client client) throws Exception {
            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 返回信息
         */
        public static Object[] doRequest(String wsUrl, String methodName, int receiveTimeout, Object... params) throws Exception {
            return doRequest(wsUrl, methodName, CONNECTION_TIMEOUT, receiveTimeout, params);
        }
    
    }
    

    总结陈词

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

    在这里插入图片描述

  • 相关阅读:
    3.4 设置环境变量MAKEFILES
    selenium 自动化测试:如何搭建自动化测试环境,搭建环境过程应该注意的问题
    【js基础】Promise专题
    笔试面试相关记录(13)
    第01章 网络数据采集入门
    iPad电容笔贵吗?开学季比较好用的ipad手写笔
    Java字符串(String类)
    区块链入门相关概念
    东京计器电控型柱塞泵比例放大器
    如何发布自己的 npm 包
  • 原文地址:https://blog.csdn.net/syb513812/article/details/140407931