Spring Boot WebService 实战解析-前言

时间:2024-04-23 07:40:15

Web Services 是特殊的网络服务,它允许位于不同地点的计算机程序通过互联网交流和共享数据。这些服务可以通过多种方式实现,其中包括使用SOAP协议和遵循REST原则。

在SOAP的实现中,Spring Web Services(Spring WS)Java API for XML Web Services(JAX-WS)是两种主要的技术选择。Spring WS 是专为简化SOAP Web Services的开发而设计的,提供了一种创建文档驱动、基于SOAP的服务的方式,特别适合需要深度集成和复杂配置的企业级应用。相比之下,JAX-WS 是遵循WS-*标准的更为基础和轻量级的Java API,适用于需要标准Java支持且配置较少的场景。

本文主要展示这两种技术在Spring Boot环境中实现方式。

本文两种实现方式

  • @Endpoint 注解实现 SOAP Web Services
  • @WebService 注解实现 SOAP Web Services

版本

  • Java:8
  • Spring Boot:2.3.12.RELEASE

@Endpoint

@Endpoint注解来自Spring Web Services(Spring WS)项目,它专门用于处理SOAP Web服务的开发。Spring WS 不依赖于企业Java的标准,而是建立在Spring框架的基础之上,提供更灵活的配置和更好的集成。


@Endpoint示例参考此篇文件:https://www.baeldung.com/spring-boot-soap-web-service。但有几个点需要改动。

1. Maven依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
    <groupId>wsdl4j</groupId>
    <artifactId>wsdl4j</artifactId>
</dependency>

2. 在src/main/resources目录下创建一个名为 countries.xsd 的XSD文件(XML Schema Definition),用于定义SOAP消息的结构。这个Schema将作为生成Java类的基础,用于请求和响应的数据结构。

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.baeldung.com/springsoap/gen"
           targetNamespace="http://www.baeldung.com/springsoap/gen" elementFormDefault="qualified">

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

    <xs:element name="getCountryResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="country" type="tns:country"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="country">
        <xs:sequence>
            <xs:element name="name" type="xs:string"/>
            <xs:element name="population" type="xs:int"/>
            <xs:element name="capital" type="xs:string"/>
            <xs:element name="currency" type="tns:currency"/>
        </xs:sequence>
    </xs:complexType>

    <xs:simpleType name="currency">
        <xs:restriction base="xs:string">
            <xs:enumeration value="GBP"/>
            <xs:enumeration value="EUR"/>
            <xs:enumeration value="PLN"/>
        </xs:restriction>
    </xs:simpleType>
</xs:schema>


3. 配置jaxb maven插件,将定义的 XSD 文件生成对应的 Java 类。

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxb2-maven-plugin</artifactId>
    <version>3.1.0</version>
    <executions>
        <execution>
            <id>xjc</id>
            <goals>
                <goal>xjc</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <sources>
            <source>src/main/resources/countries.xsd</source>
        </sources>
        <outputDirectory>src/main/java/endpoint</outputDirectory>
        <clearOutputDir>false</clearOutputDir>
    </configuration>
</plugin>

source路径自己定义,相对路径绝对路径都可以。

控制台输入命令:

mvn package

则会生成对应的java类,拷贝到对应的目录下即可。

alt

4. 添加 SOAP Web 服务端点。

@Component
@Endpoint
public class CountryEndpoint {

    private static final String NAMESPACE_URI = "http://www.baeldung.com/springsoap/gen";

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCountryRequest")
    @ResponsePayload
    public JAXBElement<GetCountryResponse> getCountry(@RequestPayload JAXBElement<GetCountryRequest> request) {
        GetCountryResponse response = new GetCountryResponse();
        Country country = new Country();
        country.setCapital("12");
        country.setName(request.getValue().getName());
        response.setCountry(country);
        return new JAXBElement<>(new QName(NAMESPACE_URI, GetCountryResponse.class.getSimpleName()), GetCountryResponse.class, response);
    }

}

注解含义:

  • @Endpoint:将类作为 Web 服务端点注册到 Spring WS。(端点可理解为处理特定类型的请求处理器)
  • @PayloadRoot:标记具体处理SOAP请求的方法。根据命名空间和 localPart 属性定义处理程序方法。
    • namespace:命名空间URI,用于标识特定的XML文档或元素集。
    • localPart:本地名称,指定在给定命名空间中的特定元素。
  • @ResponsePayload:处理将Java对象转换回SOAP消息的注解。
  • @RequestPayload:该注解将SOAP请求参数反序列化为java对象。

JAXBElement:相当于一个包装器,用来包含单个XML元素的信息。上述例子中创建JAXBElement是确保响应有正确的命令空间和本地名称。

namespace和localPart对应关系如下图:

alt

5. SOAP Web 服务配置 Bean。

WebServiceConfig类主要目的是配置和发布一个基于 SOAP 协议的 Web 服务。

@EnableWs //启用Spring Web Services的配置支持
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {

    @Bean
    public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) {  
        //定义一个servlet,用于处理soap消息
        MessageDispatcherServlet servlet = new MessageDispatcherServlet();
        servlet.setApplicationContext(applicationContext);
        servlet.setTransformWsdlLocations(true);
        return new ServletRegistrationBean<>(servlet, "/ws/*");
    }

    @Bean(name = "countries")
    public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {
        //公开一个标准的WSDL 1.1文档
        DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
        wsdl11Definition.setPortTypeName("CountriesPort");
        //定义服务的访问地址,即客户端访问Web服务的端点
        wsdl11Definition.setLocationUri("/ws");
        wsdl11Definition.setTargetNamespace("http://www.baeldung.com/springsoap/gen");
        //关联xsd文件
        wsdl11Definition.setSchema(countriesSchema);
        return wsdl11Definition;
    }

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

应用启动成功后,打开url来验证wsdl文件是否成功发布: http://localhost:18889/ws/countries.wsdl。(我本地应用端口非8080)

WSDL 文件如下:


<?xml version="1.0" encoding="UTF-8" standalone="no"?><wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:sch="http://www.baeldung.com/springsoap/gen" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.baeldung.com/springsoap/gen" targetNamespace="http://www.baeldung.com/springsoap/gen">
  <wsdl:types>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://www.baeldung.com/springsoap/gen">

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

    <xs:element name="getCountryResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="country" type="tns:country"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="country">
        <xs:sequence>
            <xs:element name="name" type="xs:string"/>
            <xs:element name="population" type="xs:int"/>
            <xs:element name="capital" type="xs:string"/>
            <xs:element name="currency" type="tns:currency"/>
        </xs:sequence>
    </xs:complexType>

    <xs:simpleType name="currency">
        <xs:restriction base="xs:string">
            <xs:enumeration value="GBP"/>
            <xs:enumeration value="EUR"/>
            <xs:enumeration value="PLN"/>
        </xs:restriction>
    </xs:simpleType>
</xs:schema>
  </wsdl:types>
  <wsdl:message name="getCountryResponse">
    <wsdl:part element="tns:getCountryResponse" name="getCountryResponse">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="getCountryRequest">
    <wsdl:part element="tns:getCountryRequest" name="getCountryRequest">
    </wsdl:part>
  </wsdl:message>
  <wsdl:portType name="CountriesPort">
    <wsdl:operation name="getCountry">
      <wsdl:input message="tns:getCountryRequest" name="getCountryRequest">
    </wsdl:input>
      <wsdl:output message="tns:getCountryResponse" name="getCountryResponse">
    </wsdl:output>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="CountriesPortSoap11" type="tns:CountriesPort">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="getCountry">
      <soap:operation soapAction=""/>
      <wsdl:input name="getCountryRequest">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="getCountryResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="CountriesPortService">
    <wsdl:port binding="tns:CountriesPortSoap11" name="CountriesPortSoap11">
      <soap:address location="http://localhost:18889/ws"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

文件内容:

types(类型):定义web服务之间传递的数据类型。
message(消息):定义web服务中用于通信的消息结构。
portType(端口类型):定义web服务接口,包含一组操作,每个操作定义了输入和输出得消息结构。
binding(绑定):定义web服务的通信协议和消息格式。
service(服务):定义web服务的实际访问地址


6. 用 postman 测试 SOAP 项目。

alt
alt

@WebService

@WebService注解来自JAX-WS(Java API for XML Web Services)标准,它是一个专门用于创建SOAP(Simple Object Access Protocol)Web服务的API。这种方法通常用于实现符合WS-标准的Web服务,并且是Java EE规范的一部分。


1. Maven依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-spring-boot-starter-jaxws</artifactId>
    <version>3.4.4</version>
</dependency>

2. 定义webservices接口。

@WebService(name = "PersonnelInfo",
        targetNamespace = "http://service.manage.com/")
public interface PersonnelInfoService {

    @WebMethod
    String syncPersonnelInfo(@WebParam(name = "xmlData", targetNamespace = "http://service.manage.com/") String jsonData);
}

@WebService:标记一个接口或类作为Web服务的服务端点。

  • name:指定服务的名称。
  • targetNamespace:定义了这个Web服务所使用的XML命名空间。

@WebMethod 和 @WebParam 注解用于定义 Web 服务中的方法和参数。


3. 定义webservices实现类。


//endpointInterface:服务接口全路径

@Slf4j
@Component
@WebService(name = "PersonnelInfo",
        targetNamespace = "http://service.manage.com/",
        endpointInterface = "com.manage.service.PersonnelInfoService")
public class PersonnelInfoServiceImpl implements PersonnelInfoService {

    @Override
    public String syncPersonnelInfo(@WebParam(name = "xmlData", targetNamespace = "http://service.manage.com/") String xmlData) {
        String result = "success";
        log.info("三方信息调用入参:{}", xmlData);
    }
}

4. 发布webservices服务。

@Configuration
public class CxfConfig {

    @Autowired
    private Bus bus;
    @Autowired
    private PersonnelInfoService personnelInfoService;

    @Bean(name = "wsBean")
    public ServletRegistrationBean dispatcherServlet() {
        //注册servlet,用于处理web服务请求
        ServletRegistrationBean wbsServlet = new ServletRegistrationBean(new CXFServlet(), "/ws/*");
        return wbsServlet;
    }

    /**
     * JAX-WS
     * 站点服务
     **/
    @Bean
    public Endpoint endpoint() {
        //发布Web服务的类,将请求都交给personnelInfoService类处理
        EndpointImpl endpoint = new EndpointImpl(bus, personnelInfoService);
        //将当前端点发布到URL路径/data上
        endpoint.publish("/data");
        return endpoint;
    }

}

输入下面url检查程序发布是否正常: http://localhost:8080/ws/countries.wsdl

alt

5. 用 postman 测试 SOAP 项目。

alt

总结

@Endpoint@WebService 这两种方式都可以实现接收 XML 请求的需求。其中一个依赖于 JAX-WS 标准,更适合定制化和特殊需求的 Web 服务实现;另一个依赖于 JAX-WS 标准,适用于遵循规范和标准化的项目。具体可根据实际需求和开发栈来选择合适的实现方式。

参考资料

  1. https://www.baeldung.com/spring-boot-soap-web-service