该指南将引导你使用 Spring 创建基于 SOAP 的 Web 服务服务器的过程。
我们将使用基于 WSDL 的 SOAP Web 服务来构建一个服务器,以暴露来自各个欧洲国家数据。
为了简化示例,我们将使用英国、西班牙及波兰等硬编码数据。
像大多数的 Spring 入门指南一样,你可以从头开始并完成每个步骤,也可以绕过你已经熟悉的基本设置步骤。如论哪种方式,你最终都有可以工作的代码。
git clone https://github.com/spring-guides/gs-soap-service.git
gs-soap-service/initial
目录;待一切就绪后,可以检查一下 gs-soap-service/complete
目录中的代码。
对于所有的 Spring 应用来说,你应该从 Spring Initializr 开始。Initializr 提供了一种快速的方法来提取应用程序所需的依赖,并为你完成许多设置。该示例需要 Spring Web 和 Spring Web Services 依赖。下图显示了此示例项目的 Initializr 设置:
上图显示了选择 Maven 作为构建工具的 Initializr。你也可以使用 Gradle。它还将
com.example
和producing-web-service
的值分别显示为 Group 和 Artifact。在本示例的其余部分,将用到这些值。
以下清单显示了选择 Maven 时创建的 pom.xml
文件:
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.2.RELEASEversion>
<relativePath/>
parent>
<groupId>com.examplegroupId>
<artifactId>producing-web-serviceartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>producing-web-servicename>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-web-servicesartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
以下清单显示了在选择 Gradle 时创建的 build.gradle
文件:
plugins {
id 'org.springframework.boot' version '2.2.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-web-services'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
pom.xml
和build.gradle
文件都显示其他构建信息,我们将在下一步中添加它们。
该项目需要在构建文件中包含 spring-ws-core
和 wsdl4j
作为依赖。
以下示例显示了使用 Maven 时需要对 pom.xml
文件进行的更改。
<dependency>
<groupId>wsdl4jgroupId>
<artifactId>wsdl4jartifactId>
dependency>
以下示例显示了使用 Gradle 时需要对 build.gradle
文件进行的更改:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-web-services'
implementation 'wsdl4j:wsdl4j:1.6.1'
jaxb("org.glassfish.jaxb:jaxb-xjc:2.2.11")
compile(files(genJaxb.classesDir).builtBy(genJaxb))
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
Web 服务域是在 XML 模式文件(XSD)中定义的,Spring-WS 会自动将其导出为 WSDL。
创建 XSD 文件及返回国家 name
、population
、capital
及 currency
的操作。以下清单(来自 src/main/resources/countries.xsd
)显示了必要的 XSD 文件:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://spring.io/guides/gs-producing-web-service"
targetNamespace="http://spring.io/guides/gs-producing-web-service" 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>
下一步是从 XSD 文件生成 Java 类。正确的方法是在构建期间使用 Maven 或 Gradle 插件自动执行该操作。
以下清单显示了 Maven 所需的插件配置:
<plugin>
<groupId>org.codehaus.mojogroupId>
<artifactId>jaxb2-maven-pluginartifactId>
<version>2.5.0version>
<executions>
<execution>
<id>xjcid>
<goals>
<goal>xjcgoal>
goals>
execution>
executions>
<configuration>
<sources>
<source>${project.basedir}/src/main/resources/countries.xsdsource>
sources>
configuration>
plugin>
生成的类放在 target/generated-sources/jaxb/
目录中。
要对 Gradle 进行同样的操作,首先需要在构建文件中配置 JAXB,如下清单所示:
configurations {
jaxb
}
bootJar {
baseName = 'gs-producing-web-service'
version = '0.1.0'
}
构建文件具有
tag
和end
注释。这些标签可以更轻松地将其提取到该指南中,以进行更详细的说明。我们不需要在自己的构建文件中使用这些注释。
下一步是添加 genJaxb
任务,Gradle 用来生成 Java 类。以下清单显示了必要的添加物:
task genJaxb {
ext.sourcesDir = "${buildDir}/generated-sources/jaxb"
ext.classesDir = "${buildDir}/classes/jaxb"
ext.schema = "src/main/resources/countries.xsd"
outputs.dir classesDir
doLast() {
project.ant {
taskdef name: "xjc", classname: "com.sun.tools.xjc.XJCTask",
classpath: configurations.jaxb.asPath
mkdir(dir: sourcesDir)
mkdir(dir: classesDir)
xjc(destdir: sourcesDir, schema: schema) {
arg(value: "-wsdl")
produces(dir: sourcesDir, includes: "**/*.java")
}
javac(destdir: classesDir, source: 1.6, target: 1.6, debug: true,
debugLevel: "lines,vars,source", includeantruntime: false,
classpath: configurations.jaxb.asPath) {
src(path: sourcesDir)
include(name: "**/*.java")
include(name: "*.java")
}
copy(todir: classesDir) {
fileset(dir: sourcesDir, erroronmissingdir: false) {
exclude(name: "**/*.java")
}
}
}
}
}
因为 Gradle 还没有 JAXB 插件,所以它涉及 Ant 任务,这使其比 Maven 复杂。
在这两种情况下,JAXB 域对象的生成过程都已连接到构建工具的生命周期中,因此无需执行其他步骤。
Country
存储库为了向 Web 服务提供数据,请创建国家/地区存储库。在该指南中,我们将创建一个具有硬编码数据的虚拟国家/地区存储库实现。以下清单(来自 src/main/java/com/example/producingwebservice/CountryRepository.java
)显示了如何执行该操作:
package com.example.producingwebservice;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
import io.spring.guides.gs_producing_web_service.Country;
import io.spring.guides.gs_producing_web_service.Currency;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class CountryRepository {
private static final Map<String, Country> countries = new HashMap<>();
@PostConstruct
public void initData() {
Country spain = new Country();
spain.setName("Spain");
spain.setCapital("Madrid");
spain.setCurrency(Currency.EUR);
spain.setPopulation(46704314);
countries.put(spain.getName(), spain);
Country poland = new Country();
poland.setName("Poland");
poland.setCapital("Warsaw");
poland.setCurrency(Currency.PLN);
poland.setPopulation(38186860);
countries.put(poland.getName(), poland);
Country uk = new Country();
uk.setName("United Kingdom");
uk.setCapital("London");
uk.setCurrency(Currency.GBP);
uk.setPopulation(63705000);
countries.put(uk.getName(), uk);
}
public Country findCountry(String name) {
Assert.notNull(name, "The country's name must not be null");
return countries.get(name);
}
}
Country
服务端点要创建服务端点,只需要一个带有一些 Spring WS 注解的 POJO 即可处理传入的 SOAP 请求。以下清单(来自 src/main/java/com/example/producingwebservice/CountryEndpoint.java
)显示了该类:
package com.example.producingwebservice;
import org.springframework.beans.factory.annotation.Autowired;
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 io.spring.guides.gs_producing_web_service.GetCountryRequest;
import io.spring.guides.gs_producing_web_service.GetCountryResponse;
@Endpoint
public class CountryEndpoint {
private static final String NAMESPACE_URI = "http://spring.io/guides/gs-producing-web-service";
private CountryRepository countryRepository;
@Autowired
public CountryEndpoint(CountryRepository countryRepository) {
this.countryRepository = countryRepository;
}
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCountryRequest")
@ResponsePayload
public GetCountryResponse getCountry(@RequestPayload GetCountryRequest request) {
GetCountryResponse response = new GetCountryResponse();
response.setCountry(countryRepository.findCountry(request.getName()));
return response;
}
}
@Endpoint
注解向 Spring WS 注册该类,作为处理传入 SOAP 消息的潜在候选者。
然后,Spring WS 使用 @PayloadRoot
注解根据消息的名称空间和 localPart
选择处理方法。
@RequestPayload
注解表示传入的消息将映射到方法的 request
参数。
@ResponsePayload
注解使 Spring WS 将返回的值映射到响应有效负载。
在所有这些代码块中,
io.spring.guides
类将在我们的 IDE 中报告编译时错误,除非我们已经运行任务以基于 WSDL 生成领域类。
使用与 Spring WS 相关的 bean 配置创建一个新类,如下面的清单(来自 src/main/java/com/example/producingwebservice/WebServiceConfig.java
)所示:
package com.example.producingwebservice;
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.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;
@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
@Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/ws/*");
}
@Bean(name = "countries")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("CountriesPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://spring.io/guides/gs-producing-web-service");
wsdl11Definition.setSchema(countriesSchema);
return wsdl11Definition;
}
@Bean
public XsdSchema countriesSchema() {
return new SimpleXsdSchema(new ClassPathResource("countries.xsd"));
}
}
MessageDispatcherServlet
。重要的是注入设置 ApplicationContext
到 MessageDispatcherServlet
。否则,Spring WS 将不会自动检测 Spring Bean。messageDispatcherServlet
不会替代 Spring Boot 的默认 DispatcherServlet
bean;DefaultMethodEndpointAdapter
配置注释驱动的 Spring WS 编程模型。这样就可以使用各种注释,例如 @Endpoint
(前面提过);DefaultWsdl11Definition
通过使用 XsdSchema
公开了标准的 WSDL 1.1。我们需要为 MessageDispatcherServlet 和 DefaultWsdl11Definition。Bean 名称确定 URL,web 服务和生成的 WSDL 文件可在该 URL 下使用。在这种情况下,WSDL 将在 http://
: 下可用。/ws/countries.wsdl
该配置还使用 WSDL 位置 servlet 转换:servlet.setTransformWsdlLocations(true)
。如果我们访问 http://localhost:8080/ws/countries.wsdl
,soap:address
将具有正确的地址。相反,如果我们从分配给计算机的面向公众的 IP 地址访问 WSDL,则会看到该地址。
Spring Boot 为我们创建一个应用类。在这种情况下,无需进一步的修改。我们可以使用它来运行该应用。以下清单(来自 src/main/java/com/example/producingwebservice/ProducingWebServiceApplication.java
)显示了应用类:
package com.example.producingwebservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProducingWebServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProducingWebServiceApplication.class, args);
}
}
@SpringBootApplication
是一个便利的注解,它添加了以下所有内容:
@Configuration
:将类标记为应用上下文 Bean 定义的源;@EnableAutoConfiguration
:告诉 Spring Boot 根据类路径配置、其他 bean 以及各种属性的配置来添加 bean。@ComponentScan
:告知 Spring 在 com/example
包中寻找他组件、配置以及服务。main()
方法使用 Spring Boot 的 SpringApplication.run()
方法启动应用。
我们可以结合 Gradle 或 Maven 来从命令行运行该应用。我们还可以构建一个包含所有必须依赖项、类以及资源的可执行 JAR 文件,然后运行该文件。在整个开发生命周期中,跨环境等等情况下,构建可执行 JAR 可以轻松地将服务作为应用进行发布、版本化以及部署。
如果使用 Gradle,则可以借助 ./gradlew bootRun
来运行应用。或通过借助 ./gradlew build
来构建 JAR 文件,然后运行 JAR 文件,如下所示:
java -jar build/libs/gs-soap-service-0.1.0.jar
由官网提供的以上这条命令的执行结果与我本地的不一样,我需要这样才能运行:
java -jar build/libs/gs-producing-web-service-0.0.1.jar
。
如果使用 Maven,则可以借助 ./mvnw spring-boot:run
来运行该用。或可以借助 ./mvnw clean package
来构建 JAR 文件,然后运行 JAR 文件,如下所示:
java -jar target/gs-soap-service-0.1.0.jar
由官网提供的以上这条命令的执行结果与我本地的不一样,我需要这样才能运行:
java -jar target/gs-producing-web-service-0.0.1.jar
。
我们还可以将 JAR 应用转换成 WAR 应用。
显示日志记录输出。该服务应在几秒内启动并运行。
现在该应用正在运行,我们可以对其进行测试。创建一个名为 request.xml
的文件,其中包含一下 SOAP 请求:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:gs="http://spring.io/guides/gs-producing-web-service">
<soapenv:Header/>
<soapenv:Body>
<gs:getCountryRequest>
<gs:name>Spaings:name>
gs:getCountryRequest>
soapenv:Body>
soapenv:Envelope>
在测试 SOAP 接口时,有几个选项。我们可以使用类似于 SoapUI 的工具,也可以在 *nix / Mac 系统上使用命令行工具。以下示例从命令行使用 curl:
# Use data from file
curl --header "content-type: text/xml" -d @request.xml http://localhost:8080/ws
# Use inline XML data
curl <<-EOF -fsSL -H "content-type: text/xml" -d @- http://localhost:8080/ws \
> response.xml && xmllint --format response.xml
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:gs="http://spring.io/guides/gs-producing-web-service">
<soapenv:Header/>
<soapenv:Body>
<gs:getCountryRequest>
<gs:name>Spain</gs:name>
</gs:getCountryRequest>
</soapenv:Body>
</soapenv:Envelope>
EOF
结果,我们应该看到以下响应:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns2:getCountryResponse xmlns:ns2="http://spring.io/guides/gs-producing-web-service">
<ns2:country>
<ns2:name>Spainns2:name>
<ns2:population>46704314ns2:population>
<ns2:capital>Madridns2:capital>
<ns2:currency>EURns2:currency>
ns2:country>
ns2:getCountryResponse>
SOAP-ENV:Body>
SOAP-ENV:Envelope>
奇怪的是,输出将是一个紧凑的 XML 文档,而不是上面显示的格式良好的文档。如果我们的系统上安装了 xmllib2,则可以使用
curl -fsSL --header "content-type: text/xml" -d @request.xml http://localhost:8080/ws > output.xml and xmllint --format output.xml
来查看格式良好的结果。
以上这条命令在 Mac 命令行不能执行,提示 curl 无法识别提供的参数。
恭喜你!我们已经使用 Spring Web Services 开发了基于 SOAP 的服务。
以下指南也可能会有所帮助:
想看指南的其他内容?请访问该指南的所属专栏:《Spring 官方指南》