Spring Boot 开发 SOAP 服务

本文介绍 Spring Boot 2 开发 SOAP 服务的方法。


目录

  • SOAP 简介
  • 开发环境
  • 基础示例
  • 总结

SOAP 简介

SOAP,Simple Object Access Protocol,简单对象访问协议,是一种基于 XML 实现网络中数据交换的通信协议。

一条 SOAP 消息就是一个普通的 XML 文档,包含以下元素:

  • 必需的 Envelope 元素,可把此 XML 文档标识为一条 SOAP 消息
  • 可选的 Header 元素,包含头部信息
  • 必需的 Body 元素,包含所有的调用和响应信息
  • 可选的 Fault 元素,提供有关在处理此消息所发生错误的信息

语法规则:

  • SOAP 消息必须用 XML 来编码
  • SOAP 消息必须使用 SOAP Envelope 命名空间
  • SOAP 消息必须使用 SOAP Encoding 命名空间
  • SOAP 消息不能包含 DTD 引用
  • SOAP 消息不能包含 XML 处理指令

SOAP 是 Web Service 三要素之一,是用来描述传递信息的格式,另外两个元素:

  • WSDL,Web Services Description Language,描述如何访问具体接口。
  • UDDI,Universal Description Discovery and Integration,管理、分发和查询 Web Service。

开发环境

  • Oracle JDK 1.8.0_201
  • Apache Maven 3.6.0
  • IntelliJ IDEA (Version 2018.3.3)

基础示例

  1. 创建 Spring Boot 工程,参考:IntelliJ IDEA 创建 Spring Boot 工程

  2. pom 文件中添加 spring-boot-starter-web-serviceswsdl4j 依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
    <groupId>wsdl4j</groupId>
    <artifactId>wsdl4j</artifactId>
    <version>1.6.3</version>
</dependency>
  1. pom 文件中添加 jaxb2-maven-plugin 插件。
<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>src/main/resources/xsd</source>
        </sources>
        <outputDirectory>src/main/java</outputDirectory>
        <packageName>tutorial.spring.boot.soap.producer.generated</packageName>
        <clearOutputDir>false</clearOutputDir>
    </configuration>
</plugin>

jaxb2-maven-plugin 能够实现 Java 类和 XML Schema 间的转换,配置说明:

  • sources:xsd 文件目录
  • outputDirectory:生成 Java 类文件的根目录
  • packageName:生成 Java 类文件的包路径
  • clearOutputDir:重新生成前是否需要清空目录

完整的 pom 文件如下:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>tutorial.spring.boot</groupId>
    <artifactId>spring-boot-soap-producer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-soap-producer</name>
    <description>Demo project for Spring Boot SOAP producer</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
        </dependency>
        <dependency>
            <groupId>wsdl4j</groupId>
            <artifactId>wsdl4j</artifactId>
            <version>1.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <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>src/main/resources/xsd</source>
                    </sources>
                    <outputDirectory>src/main/java</outputDirectory>
                    <packageName>tutorial.spring.boot.soap.producer.generated</packageName>
                    <clearOutputDir>false</clearOutputDir>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
  1. 创建 XML Schema 定义领域模型,在 src/main/resources/xsd 目录下添加 user.xsd 文件。
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:tns="http://tutorial.spring.boot/soap/produce/user"
           targetNamespace="http://tutorial.spring.boot/soap/produce/user"
           elementFormDefault="qualified">
  <xs:complexType name="User">
    <xs:sequence>
      <xs:element name="name" type="xs:string"/>
      <xs:element name="birth" type="xs:date"/>
      <xs:element name="gender" type="tns:Gender"/>
    </xs:sequence>
  </xs:complexType>

  <xs:simpleType name="Gender">
    <xs:restriction base="xs:string">
      <xs:enumeration value="Male"/>
      <xs:enumeration value="Female"/>
      <xs:enumeration value="Unknown"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:element name="UserRequest">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="name" type="xs:string"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

  <xs:element name="UserResponse">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="user" type="tns:User"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Web Service 领域模型定义在 XML Schema(XSD) 文件中,Spring-WS 会自动导出 WSDL。

  1. 执行 mvn compile 生成 Java 类文件,生成文件:
|-- src
     |-- main
          |-- java
              |-- META-INF.JAXB
                  |-- episode_xjc.xjb
              |-- tutorial.spring.boot.soap.producer
                  |-- generated
                      |-- Gender.java
                      |-- ObjectFactory.java
                      |-- package-info.java
                      |-- User.java
                      |-- UserRequest.java
                      |-- UserResponse.java
  1. 创建 Web Service 配置类。
package tutorial.spring.boot.soap.producer.config;

import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.ws.wsdl.wsdl11.Wsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;

@EnableWs
@Configuration
public class WebServiceConfig {

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

    @Bean(name = "user")
    public Wsdl11Definition defaultWsdl11Definition(XsdSchema schema) {
        DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
        wsdl11Definition.setPortTypeName("UserPort");
        wsdl11Definition.setLocationUri("/ws");
        wsdl11Definition.setTargetNamespace("http://tutorial.spring.boot/soap/produce/user");
        wsdl11Definition.setSchema(schema);
        return wsdl11Definition;
    }

    @Bean
    public XsdSchema userSchema() {
        return new SimpleXsdSchema(new ClassPathResource("xsd/user.xsd"));
    }
}

说明:

  • Spring WS 使用 MessageDispatcherServlet 处理 SOAP 消息,所以创建 Web Service 配置需要新建一个 MessageDispatcherServlet 实例,并将应用上下文 ApplicationContext 注入该实例。
  • MessageDispatcherServlet 实例命名为 messageDispatcherServlet 并不会替换 Spring Boot 默认的 DispatcherServlet bean。
  • DefaultWsdl11Definition 使用 XSD Schema 暴露标准的 WSDL 1.1。

注意:必须为 MessageDispatcherServletDefaultWsdl11Definition 实例指定名称,通过指定名称确定 WSDL URL。本文示例中 MessageDispatcherServlet 实例名称为 wsDefaultWsdl11Definition 实例名称为 user,因此 WSDL URL 是 http://<host>:<port>/ws/user.wsdl

  1. 创建服务端点:定义一个 @Endpoint 注解的 POJO 类处理传入的 SOAP 请求。
package tutorial.spring.boot.soap.producer.controller;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
import tutorial.spring.boot.soap.producer.generated.Gender;
import tutorial.spring.boot.soap.producer.generated.User;
import tutorial.spring.boot.soap.producer.generated.UserRequest;
import tutorial.spring.boot.soap.producer.generated.UserResponse;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import java.time.LocalDate;

@Endpoint
public class UserController {

    private static final String NAMESPACE_URI = "http://tutorial.spring.boot/soap/produce/user";

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "UserRequest")
    @ResponsePayload
    public UserResponse getUser(@RequestPayload UserRequest request) throws DatatypeConfigurationException {
        UserResponse response = new UserResponse();
        User user = new User();
        String name = request.getName();
        user.setName(name);
        switch (name) {
            case "Mike":
                user.setBirth(
                        DatatypeFactory.newInstance().newXMLGregorianCalendar(
                                LocalDate.of(2000, 1, 1).toString()
                        )
                );
                user.setGender(Gender.MALE);
                break;
            case "Ketty":
                user.setBirth(
                        DatatypeFactory.newInstance().newXMLGregorianCalendar(
                                LocalDate.of(2010, 12, 31).toString()
                        )
                );
                user.setGender(Gender.FEMALE);
                break;
            default:
                user.setGender(Gender.UNKNOWN);
                break;
        }
        response.setUser(user);
        return response;
    }
}

说明:

  • @Endpoint
    注解类将被注册到 Spring WS 中用于处理传入的 SOAP 消息。
  • @PayloadRoot
    Spring WS 通过此注解查找匹配消息 namespacelocalPart 的处理方法。
  • @RequestPayload
    标识传入的消息将被映射到方法的哪个入参。
  • @ResponsePayload
    此注解标识 Spring WS 将方法返回值映射到响应负载。
  1. 启动应用后使用浏览器访问 http://127.0.0.1:8080/ws/user.wsdl
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:sch="http://tutorial.spring.boot/soap/produce/user" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://tutorial.spring.boot/soap/produce/user" targetNamespace="http://tutorial.spring.boot/soap/produce/user">
  <wsdl:types>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
targetNamespace="http://tutorial.spring.boot/soap/produce/user">
      <xs:complexType name="User">
        <xs:sequence>
          <xs:element name="name" type="xs:string"/>
          <xs:element name="birth" type="xs:date"/>
          <xs:element name="gender" type="tns:Gender"/>
        </xs:sequence>
      </xs:complexType>
      <xs:simpleType name="Gender">
        <xs:restriction base="xs:string">
          <xs:enumeration value="Male"/>
          <xs:enumeration value="Female"/>
          <xs:enumeration value="Unknown"/>
        </xs:restriction>
      </xs:simpleType>
      <xs:element name="UserRequest">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="name" type="xs:string"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element name="UserResponse">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="user" type="tns:User"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:schema>
  </wsdl:types>
  <wsdl:message name="UserRequest">
    <wsdl:part element="tns:UserRequest" name="UserRequest"> </wsdl:part>
  </wsdl:message>
  <wsdl:message name="UserResponse">
    <wsdl:part element="tns:UserResponse" name="UserResponse"> </wsdl:part>
  </wsdl:message>
  <wsdl:portType name="UserPort">
    <wsdl:operation name="User">
      <wsdl:input message="tns:UserRequest" name="UserRequest"> </wsdl:input>
      <wsdl:output message="tns:UserResponse" name="UserResponse"> </wsdl:output>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="UserPortSoap11" type="tns:UserPort">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="User">
      <soap:operation soapAction=""/>
      <wsdl:input name="UserRequest">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="UserResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="UserPortService">
    <wsdl:port binding="tns:UserPortSoap11" name="UserPortSoap11">
      <soap:address location="http://127.0.0.1:8080/ws"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

从 WSDL 文件中可以看出:

  • 接口名称(wsdl:operation):User
  • 输入参数(wsdl:input):UserRequest
  • 输出参数(wsdl:output):UserResponse
  • 接口地址(soap:address):http://127.0.0.1:8080/ws
  1. 使用 Soap UI 工具测试。

总结

Spring Boot 开发 SOAP 服务的步骤:

  1. 创建 Spring Boot 应用,添加 spring-boot-starter-web-serviceswsdl4j 依赖及 jaxb2-maven-plugin 插件;
  2. 创建 xsd 文件;
  3. 执行 mvn compile 生成由 xsd 文件生产 Java 类;
  4. 创建 Web Service 配置类;
  5. 创建业务服务类;
  6. 启动应用,通过浏览器查看 wsdl 描述文件,执行测试。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351