《知识点扫盲 · 学会 WebService》

news2024/11/15 11:55:13

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

文章目录

    • 写在前面的话
    • WebService 统括
      • 技术简介
      • 常用实现
      • SB 整合 CXF
      • 实战拓展
    • 总结陈词

在这里插入图片描述

写在前面的话

博主所在公司是医疗信息化厂商,同时拥有集成平台产品线,在针对跨不同厂商的系统,通常采用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、创建服务端接口和实现类,正常些业务逻辑

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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1924628.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《斯科特·凯尔比的风光摄影手册》读书笔记

写在前面 《斯科特凯尔比的风光摄影手册》读书笔记整理没有全部读完&#xff0c;选择了感兴趣的章节理解不足小伙伴帮忙指正 &#x1f603;,生活加油 99%的焦虑都来自于虚度时间和没有好好做事&#xff0c;所以唯一的解决办法就是行动起来&#xff0c;认真做完事情&#xff0c;…

NLP之词的重要性

文章目录 何为重要词TF*IDFTF*IDF其他版本TFIDF 算法特点TF*IDF的优势TF*IDF劣势 TF*IDF的应用搜索引擎文本摘要文本相似度计算 上一篇文章介绍了新词的发现&#xff0c;用内部凝固度和左右熵来发现新词。这时候机器对一篇文章有了对词的一定理解&#xff0c;这时我们让机器上升…

云服务器重置密码后,xshell远程连接不上,重新启用密码登录方式

云服务器重置密码后 &#xff0c;xshell连接出现不能使用密码登录 解决方案&#xff1a;以下来自阿里云重新启用密码登录方式帮助文档 为轻量应用服务器创建密钥且重启服务器使密钥生效后&#xff0c;服务器会自动禁止使用root用户及密码登录。如果您需要重新启用密码登录方式&…

【python】基于决策树的语音识别

目录 引言 决策树的主要特点 决策树的构建过程 决策树的应用 数据集 代码实现 引言 决策树&#xff08;Decision Tree&#xff09;是一种常用的分类与回归方法&#xff0c;其中最为人所知的是其在分类问题上的应用。决策树模型呈树形结构&#xff0c;其中每个内部节点表…

centos7|Linux操作系统|编译最新的OpenSSL-3.3,制作rpm安装包

一、 为什么需要编译rpm包 通常&#xff0c;我们需要安装某个软件&#xff0c;尤其是在centos7这样的操作系统&#xff0c;一般是通过yum包管理器来安装软件&#xff0c;yum的作用是管理rpm包的依赖关系&#xff0c;自动的处理rpm包的安装顺序&#xff0c;安装依赖等的相关问…

【数智化案例展】沃太能源——MES系统建设引领智能制造新篇章

‍ 联想集团案例 本项目案例由联想集团投递并参与数据猿与上海大数据联盟联合推出的《2024中国数智化转型升级创新服务企业》榜单/奖项评选。 大数据产业创新服务媒体 ——聚焦数据 改变商业 沃太能源股份有限公司&#xff0c;一家在储能产品及智慧能源管理方案领域享有盛誉的…

对B-树的理解

目录 前言-为什么要使用B-树&#xff1f;B-树概念 前言-为什么要使用B-树&#xff1f; 首先&#xff0c;我们正常的搜索都有一下方式&#xff1a; 搜索二叉树&#xff0c;极端场景下会退化&#xff0c;类似于单支&#xff0c;此时的效率变成了O(N)&#xff1b;为了解决1的问题…

基于微信小程序的音乐播放平台

基于微信小程序的音乐播放平台 音乐播放小程序项目简介技术栈功能模块项目流程系统E-R图项目页面 音乐播放小程序 项目简介 微信音乐小程序旨在提供一个简洁高效的音乐播放平台&#xff0c;用户可以方便地搜索、播放和收藏自己喜欢的音乐。整个项目采用前后端分离的架构&…

Rust 测试的组织结构

测试的组织结构 本章一开始就提到&#xff0c;测试是一个复杂的概念&#xff0c;而且不同的开发者也采用不同的技术和组织。Rust 社区倾向于根据测试的两个主要分类来考虑问题&#xff1a;单元测试&#xff08;unit tests&#xff09;与 集成测试&#xff08;integration test…

基于3D感知的端到端具身操作论文导读

DexIL&#xff1a;面向双臂灵巧手柔性操作的端到端具身执行模型 模型架构 输入&#xff1a;   观测Ot&#xff1a; RGB点云&#xff0c;使用PointNet进行编码;   状态St&#xff1a; 双臂末端7x2Dof位姿16x2灵巧手关节位置&#xff0c;只进行归一化&#xff0c;无编码&am…

在word中删除endnote参考文献之间的空行

如图&#xff0c;在References中&#xff0c;每个文献之间都有空行。不建议手动删除。打开Endnote。 打开style manager 删除layout中的换行符。保存&#xff0c;在word中更新参考文献即可。

InjectFix 热更新解决方案

简介 今天来谈一谈&#xff0c;项目种的客户端热更新解决方案。InjectFix是腾讯xlua团队出品的一种用于Unity中C#代码热更新热修复的解决方案。支持Unity全系列&#xff0c;全平台。与xlua的思路类似&#xff0c;InjectFix解决的痛点主要在于Unity中C#代码写的逻辑在发包之后无…

复杂设备操作流程3D数字化全景展示好处多

传统的纸质说明书&#xff0c;尽管承载着产品的使用指南&#xff0c;但其图文平面的表现形式往往限制了表现力和说明力。对于简单产品&#xff0c;用户或许能摸索使用;但对于复杂产品&#xff0c;即使拥有详实的说明书&#xff0c;也可能因理解困难而导致使用障碍。现在&#x…

【工具使用】adb下载和配置

【工具使用】adb下载和配置 一&#xff0c;简介二&#xff0c;操作步骤2.1 Bing搜索adb2.2 下载adb工具2.3 添加路径到环境变量 三&#xff0c;效果验证 一&#xff0c;简介 本文主要介绍如何下载adb并安装使用&#xff0c;供参考。 此时打开cmd&#xff0c;输入adb 会提示&am…

<数据集>猫狗识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;3686张 标注数量(xml文件个数)&#xff1a;3686 标注数量(txt文件个数)&#xff1a;3686 标注类别数&#xff1a;2 标注类别名称&#xff1a;[cat, dog] 序号类别名称图片数框数1cat118811892dog24982498 使用标…

计算机网络复习笔记【面向考纲整理】

计算机网络复习笔记 一、计算机网络体系结构&#xff08;一&#xff09;计算机网络的概念、分类、组成与功能1.计算机网络的概念、组成与功能1.1计算机网络的概念1.2计算机网络的组成1.3计算机网络的功能 2.计算机网络的分类3.计算机网络的标准化工作及相关知识 &#xff08;二…

主机安全-进程、命令攻击与检测

目录 概述反弹shell原理nc/dev/xxx反弹shell下载不落地反弹Shell各种语言反弹shell linux提权sudosuid提权mysql提权 Dnslog参考 概述 本文更新通过在主机&#xff08;不含容器&#xff09;上直接执行命令或启动进程来攻击的场景。检测方面以字节跳动的开源HIDS elkeid举例。每…

E9.【C语言】练习:模拟用户登录界面,最多输入三次密码以及strcmp函数的讲解

思路&#xff1a;分两个环节 1.输入密码&#xff0c;存储在数组里 2.密码验证&#xff08;尝试次数不超过3次&#xff09; #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main() {char password[20] { 0 };int i 0;for ( i …

整洁架构SOLID-接口隔离原则(ISP)

文章目录 定义ISP与编程语言ISP与软件架构小结 定义 在上图中有多个用户需要操作OPS类。现在&#xff0c;我们假设这里的User1只需要使用op1,User2只需要使用op2,User3只需要使用op3。 在这种情况下&#xff0c;如果OPS类是用Java编程语言编写的&#xff0c;那么很明显&#x…

使用Gitee仓库镜像管理功能实现Gitee与Github 双向同步

进入你所需要同步的仓库&#xff0c;点击「管理」->「镜像仓库管理」&#xff0c;点击「添加镜像」选项&#xff1b; 如果你的Gitee账号还没有绑定过 GitHub 帐号&#xff0c;先根据弹窗的提示绑定 GitHub 帐号&#xff1b; 添加镜像时候&#xff0c;在「镜像方向」中选择…