《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Spring-Boot/Web-Services/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labslab-65 目录。

原创不易,给点个 Star 嘿,一起冲鸭!

1. 概述

在早期的系统中,通过 Web Services 实现服务,提供跨平台、跨语言远程调用。例如说,艿艿最近在对接的东软 HIS 项目,就是提供 Web Services 接口,让我们的系统进行调用。

友情提示:非必要情况下,胖友可以不阅读本文,因为已经很少系统需要进行 Web Services 对接。并且,老的系统也慢慢逐步切换到 RESTful 接口 + JSON 数据格式。

因为艿艿对 Web Services 了解的也并不多,所以下面的概念主要采用《维基百科 —— Web Services》的内容。

1.1 Web Services 是什么?

Web Services 可使我们的应用程序成为 Web 应用程序。并且,Web Services 可以通过 Web 进行发布、查找和使用。它的关键描述如下:

  • Web Services 是应用程序组件
  • Web Services 使用开放协议进行通信
  • Web Services 是独立的(self-contained)并可自我描述
  • Web Services 可通过使用 UDDI 来发现
  • Web Services 可被其他应用程序使用
  • XML 是 Web Services 的基础

友情提示:看到这里,胖友是不是更加懵逼了?!艿艿也是酱紫过来的,继续往下,不要慌!只要给我们一个示例,咱就能撬动整个地球!

1.2 Web Services 如何工作?

基础的 Web Services 平台是 XML + HTTP。XML 提供了一种可用于不同的平台和编程语言之间的语言。HTTP 协议是最常用的因特网协议。

1.3 Web Services 三要素?

Web Services 由三种基本元素构成:

  • SOAP(简易对象访问协议)
  • WSDL(Web Services 描述语言)
  • UDDI(通用描述、发现及整合)

1.3.1 SOAP

SOAP 是 Simple Object Access Protocol的缩写,它是一个基于 XML 的协议,让应用程序通过 HTTP 交换信息。有或者简单的说,SOAP 是一种用于访问 Web 服务的协议。

  • SOAP 指简易对象访问协议
  • SOAP 是一种通信协议
  • SOAP 用于应用程序之间的通信
  • SOAP 是一种用于发送消息的格式
  • SOAP 被设计用来通过因特网进行通信
  • SOAP 独立于平台
  • SOAP 独立于语言
  • SOAP 基于 XML
  • SOAP 很简单并可扩展
  • SOAP 允许您绕过防火墙
  • SOAP 将作为 W3C 标准来发展

友情提示:我们可以将 SOAP 和我们的 RESTful API + JSON 对等,定义了通信方式和格式

1.3.2 WSDL

WSDL 是 Web Services Description Language 的缩写,它基于 XML 的用于描述 Web Services,以及如何访问 Web Services 的语言。

  • WSDL 指网络服务描述语言
  • WSDL 使用 XML 编写
  • WSDL 是一种 XML 文档
  • WSDL 用于描述网络服务
  • WSDL 也可用于定位网络服务
  • WSDL 还不是 W3C 标准

友情提示:我们可以将 WSDL 和我们的 RESTful API 接口文档对等,定义了具体的接口信息

1.3.3 UDDI

UDDI 是 Universal Description, Discovery and Integration 的缩写,它- UDDI 指通用的描述、发现以及整合(Universal Description, Discovery and Integration)。

  • UDDI 是一种用于存储有关 Web Services 的信息的目录。
  • UDDI 是一种由 WSDL 描述的网络服务接口目录。
  • UDDI 经由 SOAP 进行通迅。
  • UDDI 被构建于 Microsoft .NET 平台之中。是一种目录服务,通过它,企业可注册并搜索 Web Services。

友情提示:我们可以将 UDDI 理解成注册中心,通过它可以获得有哪些服务以及服务的地址。

不过本文后续的内容,我们并不会涉及到 UDDI 哈~开森不?!

2. Spring Web Services

示例代码对应仓库:lab-65-spring-ws-demo

Spring Web Services 项目,是由 Spring 社区开源,专注于文档驱动的方式,构建 Web Services 服务的框架

Spring Web Services 基于契约优先理念开发 SOAP 服务,可以非常灵活的创建 Web Services 服务,并且提供多种方式处理 XML Payload。

Spring Web Services 是基于 Spring 之上,所以我们可以很方便的使用 Spring 依赖注入等等功能。

Spring Web Services (Spring-WS) is a product of the Spring community focused on creating document-driven Web services.

Spring Web Services aims to facilitate contract-first SOAP service development, allowing for the creation of flexible web services using one of the many ways to manipulate XML payloads.

The product is based on Spring itself, which means you can use the Spring concepts such as dependency injection as an integral part of your Web service.

Spring Web Services 由四个子模块组成,由下图所示:

项目依赖

spring-ws-core:它是 Spring Web Services 的核心模块,提供 WebServiceMessageSoapMessage 等核心接口,具有强大的消息分发功能的服务端框架,和用于 Web Services 端点的各种支持类。同时,它还提供了用于 Web Service 客户端WebServiceTemplate 操作类。

spring-xml:提供 Spring Web Services 的 XML 支持类。该模块主要针对 spring-ws-core 本身,而不是 Web 服务开发人员。

spring-ws-security:该模块给 spring-ws-core 安全方面的功能。通过这个模块,可以添加身份令牌、签名、加解密 SOAP 消息。另外,它可以集成 Spring Security 框架来实现认证与授权的功能。

spring-ws-support:该模块提供其它通信方式的支持,例如说 JMS、Email、XMPP 等等。

哔哔了这么多 Spring Web Services 的介绍,我们来撸个具体示例。

2.1 搭建 Web Services 服务端

创建 lab-65-spring-ws-demo-user-service 项目,基于 Spring Web Services 实现一个提供用户服务。项目结构如下图:

项目结构

2.1.1 创建 XSD 文件

创建 users.xsd 文件,描述 Web Services 消息(请求和响应)的数据结构。如下图所示:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="https://github.com/YunaiV/SpringBoot-Labs/tree/master/lab-65/lab-65-spring-ws-demo"
elementFormDefault="qualified">

<!-- 获得用户请求 -->
<xs:element name="UserGetRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="id" type="xs:int"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- 获得用户响应 -->
<xs:element name="UserGetResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="id" type="xs:int" />
<xs:element name="name" type="xs:string"/>
<xs:element name="gender" type="xs:int"/>
</xs:sequence>
</xs:complexType>
</xs:element>

<!-- 创建用户请求 -->
<xs:element name="UserCreateRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="gender" type="xs:int"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- 创建用户响应 -->
<xs:element name="UserCreateResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="id" type="xs:int"/>
</xs:sequence>
</xs:complexType>
</xs:element>

</xs:schema>

① 在 <xs:schema /> 标签中,设置了 targetNamespace 属性,声明 Web Services 所在 Namespace。具体的值,可以按照自己的喜好来设置,后续会不断使用到。

② 所有 WebService 的请求必须以 Request 结尾,响应必须以 Response 结尾。

2.1.2 引入依赖

创建 pom.xml 文件,引入 Spring Web Services 相关依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>lab4-64-spring-ws-demo</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>lab-65-spring-ws-demo-user-service</artifactId>

<properties>
<!-- 依赖相关配置 -->
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
<!-- 插件相关配置 -->
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- 实现对 Spring Web Services 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>

<!-- Java WSDL 实现库 -->
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<!-- jaxb2-maven-plugin 插件,用于实现将 XML 生成目标类 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>2.5.0</version>
<executions>
<execution>
<id>xjc</id>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- 源文件地址 -->
<sources>
<source>${project.basedir}/src/main/resources/users.xsd</source>
</sources>
<!-- 生成代码目标包 -->
<packageName>cn.iocoder.springboot.lab65.userservice.model</packageName>
</configuration>
</plugin>
</plugins>
</build>

</project>

① 引入 spring-boot-starter-web-services 依赖,从而引入 Spring Web Services 相关依赖,并实现其自动配置。

② 引入 wsdl4j 依赖,Java WSDL 实现库。

③ 引入 jaxb2-maven-plugin 插件,用于实现将「2.1.1 创建 XSD 文件」生成具体的 Web Services 请求和响应的类。

  • <source /> 标签,设置 XSD 文件所在目录。
  • <packageName /> 标签,设置生成的 Web Services 请求和响应的类所在 package 包。

然后,我们来使用 jaxb2-maven-plugin 进行下生成。如下图所示:

生成结果

2.1.3 WebServicesConfig

创建 WebServicesConfig 配置类,创建 Spring Web Services 相关 Bean。代码如下:

@Configuration
@EnableWs // 开启 Web Services 服务
public class WebServicesConfig extends WsConfigurerAdapter {

public static final String NAMESPACE_URI = "https://github.com/YunaiV/SpringBoot-Labs/tree/master/lab-65/lab-65-spring-ws-demo";

@Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<>(servlet, "/ws/*");
}

@Bean
public XsdSchema usersSchema() {
return new SimpleXsdSchema(new ClassPathResource("users.xsd"));
}

@Bean(name = "users")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema usersSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace(NAMESPACE_URI);
wsdl11Definition.setSchema(usersSchema);
wsdl11Definition.setPortTypeName("UsersPort");
return wsdl11Definition;
}

@Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
// 可自定义附加拦截器
}

}

① 在类上添加 @EnableWs 注解,开启 Spring Web Services 服务。

#messageDispatcherServlet(...) 方法,用于创建用于消息分发的 MessageDispatcherServlet Bean。它的功能和 SpringMVC DispatcherServlet 是一致的,处理流程如下图所示:

MessageDispatcherServlet 处理流程

#defaultWsdl11Definition(...) 方法,基于「2.1.1 创建 XSD 文件」构建一个 WSDL 文件。稍后我们来具体瞅瞅~

另外要注意,#defaultWsdl11Definition(...) 方法上的 @Bean 注解设置的 name 名字,决定了 WSDL 最终访问的地址。例如说,此处就对应 http://127.0.0.1:8080/ws/users.wsdl 地址。

2.1.4 UserEndpoint

创建 UserEndpoint 类,提供用户服务 Web Services 端点。代码如下:

@Endpoint
public class UserEndpoint {

@PayloadRoot(namespace = WebServicesConfig.NAMESPACE_URI, localPart = "UserGetRequest")
@ResponsePayload
public UserGetResponse get(@RequestPayload UserGetRequest request) {
UserGetResponse response = new UserGetResponse();
response.setId(request.getId());
response.setName("没有昵称:" + request.getId());
response.setGender(request.getId() % 2 + 1);
return response;
}

@PayloadRoot(namespace = WebServicesConfig.NAMESPACE_URI, localPart = "UserCreateRequest")
@ResponsePayload
public UserCreateResponse create(@RequestPayload UserCreateRequest request) {
UserCreateResponse response = new UserCreateResponse();
response.setId((int) (System.currentTimeMillis() / 1000));
return response;
}

}

① 在类上,添加 @Endpoint 注解,声明 UserEndpoint 是一个 Spring Web Services 的端点。

② 在方法上添加 @PayloadRoot 注解,声明该方法对应指定 namespace 下的指定类型 localPart 的请求。

③ 在方法参数上,添加 @RequestPayload 注解,设置其对应请求

④ 在方法上,添加 @ResponsePayload 注解,设置方法的返回为响应

2.1.5 UserServiceApplication

创建 UserServiceApplication 类,用户服务启动类。代码如下:

@SpringBootApplication
public class UserServiceApplication {

public static void main(String[] args) {
// 启动 Spring Boot 应用
SpringApplication.run(UserServiceApplication.class, args);
}

}

2.1.6 简单测试

① 执行 UserServiceApplication 类,启动成功,打印如下日志:

2020-06-16 08:29:44.463  INFO 70202 --- [           main] .w.s.a.s.AnnotationActionEndpointMapping : Supporting [WS-Addressing August 2004, WS-Addressing 1.0]

② 使用浏览器访问 http://127.0.0.1:8080/ws/users.wsdl 地址,可以看到 WSDL 的内容。如下图所示:

WSDL 内容

2.1.7 SoapUI 测试

下面,我们来使用 SoapUI 进行具体的测试。

SoapUI 提供一个工具通过 SOAP/HTTP 来检查,调用,实现 Web Services 的功能/负载/符合性测试。

① 使用浏览器打开 https://www.soapui.org/downloads/soapui/ 地址,下载 SoapUI Open Source 免费版。如下图所示:

SoapUI 下载地址

下载完成后,点击进行安装。

② 安装完成,点击 SoapUI 打开,进入 SoapUI 主界面。如下图所示:

SoapUI 主界面

③ 点击上方「SOAP」按钮,在「Initial WSDL」输入框中填入 http://127.0.0.1:8080/ws/users.wsdl 地址。如下图所示:

SoapUI 主界面 —— SOAP

点击 OK 确认,完成 SOAP 的导入。如下图所示:

SoapUI 主界面 —— SOAP 示例

④ 点击 Request 1,弹出测试窗口,用于模拟 Web Services 请求。如下图所示:

SoapUI 主界面 —— Request 1

<lab:name /><lab:gender /> 标签中,填写一下,然后点击执行按钮,模拟一次 Web Services 请求。如下图所示:

SoapUI 主界面 —— Request 1 模拟

2.1.8 Postman 测试

在上述的学习中,胖友应该发现,Web Services 本质上就是 HTTP + XML 的组合。因此,我们可以直接使用 Postman 进行模拟测试。如下图所示:

Postman 模拟

具体的请求内容,是可以从 SOAP 请求内容如下:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:lab="https://github.com/YunaiV/SpringBoot-Labs/tree/master/lab-65/lab-65-spring-ws-demo">
<soapenv:Header/>
<soapenv:Body>
<lab:UserCreateRequest>
<lab:name>芋道源码</lab:name>
<lab:gender>2</lab:gender>
</lab:UserCreateRequest>
</soapenv:Body>
</soapenv:Envelope>

2.2 搭建 Web Services 客户端

创建 lab-65-spring-ws-demo-application 项目,基于 Spring Web Services 实现客户端,进行用户服务的调用。项目结构如下图:

项目结构

2.2.1 引入依赖

创建 pom.xml 文件,引入 Spring Web Services 相关依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>lab4-64-spring-ws-demo</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>lab-65-spring-ws-demo-application</artifactId>

<properties>
<!-- 依赖相关配置 -->
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
<!-- 插件相关配置 -->
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- 实现对 Spring WebService 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>

<!-- Java WSDL 实现库 -->
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<!-- maven-jaxb2-plugin 插件,用于实现将 WSDL 生成目标类 -->
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.14.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- WSDL 源文件地址 -->
<schemaLanguage>WSDL</schemaLanguage>
<schemas>
<schema>
<url>http://127.0.0.1:8080/ws/users.wsdl</url>
</schema>
</schemas>
<!-- 生成代码目标包 -->
<generatePackage>cn.iocoder.springboot.lab65.demo.wsdl</generatePackage>
</configuration>
</plugin>
</plugins>
</build>

</project>

① 和「2.1 搭建 Webb Services 客户端」一样,需要引入 spring-boot-starter-web-serviceswsdl4j 依赖。

② 引入 maven-jaxb2-plugin 插件,用于实现将用户服务提供的 WSDL 文件,生成具体的 Web Services 请求和响应的类。

  • <schemaLanguage /><schema /> 标签,设置 XSD 文件所在地址。这里,我们设置为用户服务提供的 http://127.0.0.1:8080/ws/users.wsdl 地址。
  • <generatePackage /> 标签,设置生成的 Web Services 请求和响应的类所在 package 包。

然后,我们来使用 jaxb2-maven-plugin 进行下生成。如下图所示:

生成结果

2.2.2 配置文件

创建 application.yml 配置文件,设置端口为 9090,避免冲突。

server:
port: 9090

2.2.3 UserClient

创建 UserClient 类,创建用户服务的 Web Services 客户端。代码如下:

public class UserClient extends WebServiceGatewaySupport {

public static final String WEB_SERVICES_URI = "http://127.0.0.1:8080/ws";

public UserGetResponse getUser(Integer id) {
// 创建请求对象
UserGetRequest request = new UserGetRequest();
request.setId(id);
// 执行请求
return (UserGetResponse) getWebServiceTemplate().marshalSendAndReceive(request);
}

public UserCreateResponse createUser(String name, Integer gender) {
// 创建请求对象
UserCreateRequest request = new UserCreateRequest();
request.setName(name);
request.setGender(gender);
// 执行请求
return (UserCreateResponse) getWebServiceTemplate().marshalSendAndReceive(request);
}

}

① 继承 WebServiceGatewaySupport 类,可以获得 Spring Web Service 客户端WebServiceTemplate 操作类。

② 在每个方法中,通过调用 WebServiceTemplate 的 #marshalSendAndReceive(final Object requestPayload) 方法,实现 Web Services 调用。

2.2.4 WebServicesConfig

创建 WebServicesConfig 配置类,创建「2.2.3 UserClient」Bean 对象。代码如下:

@Configuration
public class WebServicesConfig {

// 创建 Jaxb2Marshaller Bean,实现 XML 与 Bean 的互相转换
@Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("cn.iocoder.springboot.lab65.demo.wsdl"); // 用户服务的 WSDL 文件
return marshaller;
}

// 创建 UserClient Bean
@Bean
public UserClient countryClient(Jaxb2Marshaller marshaller) {
UserClient client = new UserClient();
client.setDefaultUri("http://127.0.0.1:8080/ws"); // 用户服务的 Web Services 地址
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
return client;
}

}

2.2.5 DemoController

创建 DemoController 类,提供示例 API。代码如下:

@RestController
@RequestMapping("/demo")
public class DemoController {

@Autowired
private UserClient userClient;

@GetMapping("/get")
public String get(@RequestParam("id") Integer id) {
// 执行 Web Services 请求
UserGetResponse response = userClient.getUser(id);
// 响应
return response.getName();
}

@GetMapping("/create") // 为了方便测试,实际使用 @PostMapping
public Integer create(@RequestParam("name") String name,
@RequestParam("gender") Integer gender) {
// 执行 Web Services 请求
UserCreateResponse response = userClient.createUser(name, gender);
// 响应
return response.getId();
}

}

代码比较简单,胖友一瞅就明白。

2.2.6 DemoApplication

创建 DemoApplication 类,项目启动类。代码如下:

@SpringBootApplication
public class FeignDemoApplication {

public static void main(String[] args) {
SpringApplication.run(FeignDemoApplication.class, args);
}

}

2.2.7 简单测试

① 执行 DemoApplication 类,启动示例项目。

② 使用浏览器,访问 http://127.0.0.1:9090/demo/get?id=1 地址,成功执行 Web Services 调用。返回结果如下:

没有昵称:1

③ 使用浏览器,访问 http://127.0.0.1:9090/demo/create?name=yudaoyuanma&gender=1 地址,成功执行 Web Services 调用。返回结果如下:

1592281617

3. Feign SOAP 集成

示例代码对应仓库:lab-65-ws-feign-client

《芋道 Spring Boot 声明式调用 Feign 入门》文章中,我们学习了如何使用 Feign 实现声明式调用。

Feign 是由 Netflix 开源的声明式的 HTTP 客户端,目前已经捐献给 OpenFeign 社区。

Feign 的子项目 Feign SOAP 提供了 Web Services 调用的能力,也就是说可以作为 Web Services 的客户端。

Feign SOAP 代码

下面,我们新建 lab-65-ws-feign-client 示例项目,使用 Feign 作为客户端,调用「2. Spring Web Services」小节的用户服务。最终项目如下图所示:

项目结构

3.1 引入依赖

创建 pom.xml 文件,引入 Feign 相关依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>lab-65</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>lab-65-ws-feign-client</artifactId>

<properties>
<!-- 依赖相关配置 -->
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
<!-- 插件相关配置 -->
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>


<dependencies>
<!-- 引入 SpringMVC 相关依赖,并实现对其的自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 引入 Feign SOAP 拓展的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-soap</artifactId>
<version>11.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<!-- maven-jaxb2-plugin 插件,用于实现将 WSDL 生成目标类 -->
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.14.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- WSDL 源文件地址 -->
<schemaLanguage>WSDL</schemaLanguage>
<schemas>
<schema>
<url>http://127.0.0.1:8080/ws/users.wsdl</url>
</schema>
</schemas>
<!-- 生成代码目标包 -->
<generatePackage>cn.iocoder.springboot.lab65.demo.wsdl</generatePackage>
</configuration>
</plugin>
</plugins>
</build>

</project>

① 引入 feign-soap 依赖,提供 Feign 针对 SOAP 的拓展。

② 引入 maven-jaxb2-plugin 依赖,用于实现将 WSDL 生成目标类。

然后,我们来使用 jaxb2-maven-plugin 进行下生成。如下图所示:

生成结果

3.2 UserServiceFeignClient

创建 UserServiceFeignClient 类,创建用户服务的 Web Services 客户端。代码如下:

public interface UserServiceFeignClient {

// 获得用户详情
@RequestLine("POST /")
@Headers("Content-Type: text/xml")
UserGetResponse getUser(UserGetRequest request);

// 创建用户
@RequestLine("POST /")
@Headers("Content-Type: text/xml")
UserCreateResponse createUser(UserCreateRequest request);

}

① 添加 @RequestLine("POST /") 注解,设置 POST 请求根路径。

② 添加 @Headers("Content-Type: text/xml") 注解,因为 Web Services 的请求数据格式是 XML。

③ 方法的入参和返回,使用 maven-jaxb2-plugin 插件生成的类。

3.3 FeignConfig

创建 FeignConfig 配置类,创建 UserServiceFeignClient 代理对象。代码如下:

@Configuration
public class FeignConfig {

private static final JAXBContextFactory JAXB_FACTORY = new JAXBContextFactory.Builder()
.withMarshallerJAXBEncoding("UTF-8")
.build();

@Bean
public UserServiceFeignClient userServiceFeignClient() {
return Feign.builder()
.encoder(new SOAPEncoder(JAXB_FACTORY))
.decoder(new SOAPDecoder(JAXB_FACTORY))
.target(UserServiceFeignClient.class, "http://127.0.0.1:8080/ws"); // 目标地址
}

}

Feign 客户端的编解码器 SOAPEncoderSOAPDecoder,就是由 Feign SOAP 所提供。

3.4 DemoController

创建 DemoController 类,提供示例 API。代码如下:

@RestController
@RequestMapping("/demo")
public class DemoController {

@Autowired
private UserServiceFeignClient userClient;

@GetMapping("/get")
public String get(@RequestParam("id") Integer id) {
// 请求
UserGetRequest request = new UserGetRequest();
request.setId(id);
// 执行 Web Services 请求
UserGetResponse response = userClient.getUser(request);
// 响应
return response.getName();
}

@GetMapping("/create") // 为了方便测试,实际使用 @PostMapping
public Integer create(@RequestParam("name") String name,
@RequestParam("gender") Integer gender) {
// 请求
UserCreateRequest request = new UserCreateRequest();
request.setName(name);
request.setGender(gender);
// 执行 Web Services 请求
UserCreateResponse response = userClient.createUser(request);
// 响应
return response.getId();
}

}

代码比较简单,胖友一瞅就明白。

3.5 配置文件

创建 application.yml 配置文件,设置端口为 9090,避免冲突。

server:
port: 9090

3.6 FeignDemoApplication

创建 FeignDemoApplication 类,项目启动类。代码如下:

@SpringBootApplication
public class FeignDemoApplication {

public static void main(String[] args) {
SpringApplication.run(FeignDemoApplication.class, args);
}

}

3.7 简单测试

① 执行 DemoApplication 类,启动示例项目。

② 使用浏览器,访问 http://127.0.0.1:9090/demo/get?id=1 地址,成功执行 Web Services 调用。返回结果如下:

没有昵称:1

③ 使用浏览器,访问 http://127.0.0.1:9090/demo/create?name=yudaoyuanma&gender=1 地址,成功执行 Web Services 调用。返回结果如下:

1592354015

4. Apache CXF

示例代码对应仓库:lab-65-cxf-ws-demo

除了使用「2. Spring Web Services」实现 Web Services 之外,我们也可以使用 Apache CXF 框架。

Apache CXF 是一个开源的服务框架。通过使用 JAX-WSJAX-RS 等等 API,我们基于 CXF 构建和开发服务。

这些服务可以使用各种协议,如 SOAP、XML/HTTP、RESTful HTTP 或 CORBA,并在各种通信方式(如 HTTP、JMS 或 JBI)上工作。

😈 一看概念就懵逼,我们还是来撸个 CXF 的具体示例。

4.1 搭建 Web Services 服务端

创建 lab-65-spring-ws-demo-user-service 项目,基于 Spring Web Services 实现一个提供用户服务。项目结构如下图:

项目结构

4.1.1 引入依赖

创建 pom.xml 文件,引入 CXF 相关依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>lab-65-spring-ws-demo</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>lab-65-cxf-ws-demo-user-service</artifactId>

<properties>
<!-- 依赖相关配置 -->
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
<!-- 插件相关配置 -->
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- 实现 CXF 对 Web Services 的自动配置 -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.3.6</version>
</dependency>
</dependencies>

</project>

添加 cxf-spring-boot-starter-jaxws 依赖,引入 CXF 依赖,并实现对 Web Services 的自动配置。

4.1.2 配置文件

创建 application.yml 配置文件,添加 CXF 配置。

# CXF 配置项,对应 CxfProperties 配置类
cxf:
path: /ws/ # CXF CXFServlet 的匹配路径

server:
port: 9090 # 设置服务器端口为 9090

cxf 配置项,对应 CxfProperties 配置类。

通过 cxf.path 配置项,设置 CXF CXFServlet 匹配路径是 /ws/ 开头。其中,CXFServlet 负责分发 CXF Web Services 的请求,和 SpringMVC DispatcherServlet 的功能是一致的。

server.port 配置项,设置服务器端口为 9090。

4.1.3 UserService

创建 UserService 接口,定义用户服务接口。代码如下:

@WebService(targetNamespace = CXFConfig.NAMESPACE_URI)
public interface UserService {

UserGetResponse get(UserGetRequest request);

UserCreateResponse create(UserCreateRequest request);

}

① 在接口上,添加了 @WebService 注解,声明它是一个 Web Services 接口。

  • targetNamespace 属性:设置 Namespace 命名空间。这里我们设置为 CXFConfig.NAMESPACE_URI

    // CXFConfig.java

    public static final String NAMESPACE_URI = "https://github.com/YunaiV/SpringBoot-Labs/tree/master/lab-65/lab-65-cxf-ws-demo";
    • 具体 Namespace 命名空间的,胖友可以根据自己的喜好~

@WebService 注解是 JAX-WS 规范所定义,而 Apache CXF 实现了对该规范的支持,所以本小节的示例都是使用 JAX-WS 的注解来声明 Web Service 服务的噢。

JAX-WS 全称是 Java API for XML-Based Web Services。

JAX-WS 是一种编程模型,它通过注解的方式,简化 Web Services 的开发。

更多 JAX-WS 注解的说明,可以看看《Web Services 注解总结》文章。

4.1.4 UserServiceImpl

创建 UserServiceImpl 类,实现「4.1.3 UserService」接口,用户服务的具体实现。代码如下:

@Service
@WebService(
serviceName = "userService", // 服务名称
targetNamespace = CXFConfig.NAMESPACE_URI // WSDL 命名空间
)
public class UserServiceImpl implements UserService {

@Override
public UserGetResponse get(UserGetRequest request) {
UserGetResponse response = new UserGetResponse();
response.setId(request.getId());
response.setName("没有昵称:" + request.getId());
response.setGender(request.getId() % 2 + 1);
return response;
}

@Override
public UserCreateResponse create(UserCreateRequest request) {
UserCreateResponse response = new UserCreateResponse();
response.setId((int) (System.currentTimeMillis() / 1000));
return response;
}

}

① 在类上,添加 Spring @Service 注解,注解到 Spring 容器中。

② 在类上,还是要添加 @WebService 注解,声明它是一个 Web Services 实现。

  • serviceName 属性:Web Services 服务名称。
  • targetNamespace 属性:设置 Namespace 命名空间,保持和接口上的 @WebService 注解的 targetNamespace 属性一致即可。

艿艿也不早为啥 targetNamespace 属性要接口和实现类都设置一次,反正不设置的话,Web Services 调用会报错。

4.1.5 CXFConfig

创建 CXFConfig 类,进行 CXF 的配置。代码如下:

@Configuration
public class CXFConfig {

public static final String NAMESPACE_URI = "https://github.com/YunaiV/SpringBoot-Labs/tree/master/lab-65/lab-65-cxf-ws-demo";

@Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
return new SpringBus();
}

@Bean
public Endpoint userServiceEndpoint(UserService userService) {
Endpoint endpoint = Endpoint.create(userService);
endpoint.publish("/user");//发布地址
return endpoint;
}

}

#springBus() 方法,创建一个 SpringBus Bean,用于 Web Services 服务的发布。注意,Bean 的名字一定要是 Bus.DEFAULT_BUS_ID

#userServiceEndpoint(...) 方法,创建一个 Endpoint Bean,将 UserService 发布到 /user 路径下。这样,我们后续访问 http://127.0.0.1:9090/ws/user 地址时,就是调用 UserService 用户服务。

4.1.6 UserServiceApplication

创建 UserServiceApplication 类,用户服务启动类。代码如下:

@SpringBootApplication
public class UserServiceApplication {

public static void main(String[] args) {
// 启动 Spring Boot 应用
SpringApplication.run(UserServiceApplication.class, args);
}

}

4.1.7 简单测试

① 执行 UserServiceApplication 类,启动成功,打印 Web Services 相关如下日志:

2020-06-17 07:50:10.327  INFO 94728 --- [           main] o.a.c.w.s.f.ReflectionServiceFactoryBean : Creating Service {https://github.com/YunaiV/SpringBoot-Labs/tree/master/lab-65/lab-65-cxf-ws-demo}userService from class cn.iocoder.springboot.lab65.userservice.service.UserService
2020-06-17 07:50:10.768 INFO 94728 --- [ main] org.apache.cxf.endpoint.ServerImpl : Setting the server's publish address to be /user

② 使用浏览器访问 http://127.0.0.1:9090/ws/user?wsdl 地址,可以看到 WSDL 的内容。如下图所示:

WSDL 内容

③ 使用 SoapUI 进行 Web Services 接口测试,示例如下图所示:

SoapUI 测试示例

④ 也可以使用 Postman 进行 Web Services 接口测试,示例如下图所示:

Postman 测试示例

4.2 搭建 Web Services 客户端

创建 lab-65-cxf-ws-demo-application 项目,基于 CXF 实现 Web Services 客户端,进行用户服务的调用。项目结构如下图:

项目结构

4.2.1 WSDL 文件

resources 目录,先创建 wsdl 目录,再创建 user.wsdl 文件,内容来自 http://127.0.0.1:9090/ws/user?wsdl 地址提供的用户服务的 WSDL 内容。如下图所示:

`user.wsdl` 文件

稍后,我们将使用 user.wsdl 自动生成 CXF 的代码,用于调用用户服务。

4.2.2 引入依赖

创建 pom.xml 文件,引入 CXF 相关依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>lab-65-cxf-ws-demo</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>lab-65-cxf-ws-demo-application</artifactId>

<properties>
<!-- 依赖相关配置 -->
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
<!-- 插件相关配置 -->
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- 实现 CXF 对 Web Services 的自动配置 -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.3.6</version>
</dependency>
</dependencies>

<build>
<plugins>
<!-- cxf-codegen-plugin 插件,用于实现将 WSDL 生成目标类 -->
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>3.2.5</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<!-- WSDL 源文件地址 -->
<wsdlOptions>
<wsdlOption>
<wsdl>src/main/resources/wsdl/user.wsdl</wsdl>
<wsdlLocation>classpath:wsdl/user.wsdl</wsdlLocation>
</wsdlOption>
</wsdlOptions>
<!-- 生成 Java 代码目录 -->
<sourceRoot>${project.build.directory}/generated/cxf</sourceRoot>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>

① 添加 cxf-spring-boot-starter-jaxws 依赖,引入 CXF 依赖,并实现对 Web Services 的自动配置。

② 引入 cxf-codegen-plugin 插件,用于实现将用户服务提供的 WSDL 文件,生成具体的 CXF 的代码。

然后,我们来使用 cxf-codegen-plugin 进行下生成。如下图所示:

生成结果

4.2.3 CXFConfig

创建 CXFConfig 类,创建 UserService Bean。代码如下:

@Configuration
public class CXFConfig {

@Bean
public UserService userService() {
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
// 设置 UserService 接口
jaxWsProxyFactoryBean.setServiceClass(UserService.class);
// 设置 Web Services 地址
jaxWsProxyFactoryBean.setAddress("http://127.0.0.1:9090/ws/user");
// 创建
return (UserService) jaxWsProxyFactoryBean.create();
}

}

#userService() 方法中,通过 JaxWsProxyFactoryBean,基于 UserService 接口,创建调用用户服务的 Web Services 代理

4.2.4 DemoController

创建 DemoController 类,提供示例 API。代码如下:

@RestController
@RequestMapping("/demo")
public class DemoController {

@Autowired
private UserService userService;

@GetMapping("/get")
public String get(@RequestParam("id") Integer id) {
UserGetRequest request = new UserGetRequest();
request.setId(id);
// 执行 Web Services 请求
UserGetResponse response = userService.get(request);
// 响应
return response.getName();
}

@GetMapping("/create") // 为了方便测试,实际使用 @PostMapping
public Integer create(@RequestParam("name") String name,
@RequestParam("gender") Integer gender) {
// 请求
UserCreateRequest request = new UserCreateRequest();
request.setName(name);
request.setGender(gender);
// 执行 Web Services 请求
UserCreateResponse response = userService.create(request);
// 响应
return response.getId();
}

}

代码比较简单,胖友一瞅就明白。

4.2.5 DemoApplication

创建 DemoApplication 类,项目启动类。代码如下:

@SpringBootApplication
public class FeignDemoApplication {

public static void main(String[] args) {
SpringApplication.run(FeignDemoApplication.class, args);
}

}

4.2.6 简单测试

① 执行 DemoApplication 类,启动成功,打印 Web Services 相关如下日志:

# 创建 UserService 代理的日志
2020-06-17 09:19:38.181 INFO 95812 --- [ main] o.a.c.w.s.f.ReflectionServiceFactoryBean : Creating Service {https://github.com/YunaiV/SpringBoot-Labs/tree/master/lab-65/lab-65-cxf-ws-demo}UserServiceService from class https.github_com.yunaiv.springboot_labs.tree.master.lab_65.lab_65_cxf_ws_demo.UserService

② 使用浏览器,访问 http://127.0.0.1:8080/demo/get?id=1 地址,成功执行 Web Services 调用。返回结果如下:

没有昵称:1

③ 使用浏览器,访问 http://127.0.0.1:8080/demo/create?name=yudaoyuanma&gender=1 地址,成功执行 Web Services 调用。返回结果如下:

1592281617

666. 彩蛋

断断续续花了两天时间,相对完整的学习了 Web Services 的知识,补齐了这块知识和历史的短板。虽然说 Web Services 已经慢慢淡出历史,但是不影响我们去感受它的美好与不足。

这里,在推荐两篇不错的内容,艿艿在学习的过程中,对我帮助特别大。

文章目录
  1. 1. 1. 概述
    1. 1.1. 1.1 Web Services 是什么?
    2. 1.2. 1.2 Web Services 如何工作?
    3. 1.3. 1.3 Web Services 三要素?
      1. 1.3.1. 1.3.1 SOAP
      2. 1.3.2. 1.3.2 WSDL
      3. 1.3.3. 1.3.3 UDDI
  2. 2. 2. Spring Web Services
    1. 2.1. 2.1 搭建 Web Services 服务端
      1. 2.1.1. 2.1.1 创建 XSD 文件
      2. 2.1.2. 2.1.2 引入依赖
      3. 2.1.3. 2.1.3 WebServicesConfig
      4. 2.1.4. 2.1.4 UserEndpoint
      5. 2.1.5. 2.1.5 UserServiceApplication
      6. 2.1.6. 2.1.6 简单测试
      7. 2.1.7. 2.1.7 SoapUI 测试
      8. 2.1.8. 2.1.8 Postman 测试
    2. 2.2. 2.2 搭建 Web Services 客户端
      1. 2.2.1. 2.2.1 引入依赖
      2. 2.2.2. 2.2.2 配置文件
      3. 2.2.3. 2.2.3 UserClient
      4. 2.2.4. 2.2.4 WebServicesConfig
      5. 2.2.5. 2.2.5 DemoController
      6. 2.2.6. 2.2.6 DemoApplication
      7. 2.2.7. 2.2.7 简单测试
  3. 3. 3. Feign SOAP 集成
    1. 3.1. 3.1 引入依赖
    2. 3.2. 3.2 UserServiceFeignClient
    3. 3.3. 3.3 FeignConfig
    4. 3.4. 3.4 DemoController
    5. 3.5. 3.5 配置文件
    6. 3.6. 3.6 FeignDemoApplication
    7. 3.7. 3.7 简单测试
  4. 4. 4. Apache CXF
    1. 4.1. 4.1 搭建 Web Services 服务端
      1. 4.1.1. 4.1.1 引入依赖
      2. 4.1.2. 4.1.2 配置文件
      3. 4.1.3. 4.1.3 UserService
      4. 4.1.4. 4.1.4 UserServiceImpl
      5. 4.1.5. 4.1.5 CXFConfig
      6. 4.1.6. 4.1.6 UserServiceApplication
      7. 4.1.7. 4.1.7 简单测试
    2. 4.2. 4.2 搭建 Web Services 客户端
      1. 4.2.1. 4.2.1 WSDL 文件
      2. 4.2.2. 4.2.2 引入依赖
      3. 4.2.3. 4.2.3 CXFConfig
      4. 4.2.4. 4.2.4 DemoController
      5. 4.2.5. 4.2.5 DemoApplication
      6. 4.2.6. 4.2.6 简单测试
  5. 5. 666. 彩蛋