创建 SOAP Web 服务

本指南将引导您完成使用 Spring 创建基于 SOAP 的 Web 服务服务器的过程。

您将构建什么

您将构建一个服务器,该服务器通过基于 WSDL 的 SOAP Web 服务公开来自欧洲各国的数据。

为了简化示例,您将对英国、西班牙和波兰使用硬编码数据。

您需要什么

如何完成本指南

与大多数 Spring 入门指南一样,您可以从头开始并完成每个步骤,或者绕过您已经熟悉的基本设置步骤。无论哪种方式,您都能得到可工作的代码。

从头开始,请继续阅读从 Spring Initializr 开始

跳过基础部分,请执行以下操作

完成后,您可以将您的结果与 gs-soap-service/complete 中的代码进行对照。

从 Spring Initializr 开始

您可以使用此预初始化项目,然后单击 Generate 下载 ZIP 文件。该项目已配置好,适合本教程中的示例。

手动初始化项目

  1. 导航到 https://start.spring.io。该服务会拉取应用程序所需的所有依赖项,并为您完成大部分设置。

  2. 选择 Gradle 或 Maven 以及您想使用的语言。本指南假设您选择了 Java。

  3. 单击 Dependencies 并选择 Spring WebSpring Web Services

  4. 单击 Generate

  5. 下载生成的 ZIP 文件,这是一个已根据您的选择配置好的 Web 应用程序存档。

如果您的 IDE 集成了 Spring Initializr,您可以直接从 IDE 完成此过程。
pom.xmlbuild.gradle 文件都需要额外的构建信息,您将在下一步添加。
您也可以从 Github fork 项目并在您的 IDE 或其他编辑器中打开。

添加 Spring-WS 依赖项

项目需要在构建文件中包含 spring-ws-corewsdl4j 作为依赖项。

以下示例展示了如果您使用 Maven 需要对 pom.xml 文件进行的更改

<dependency>
	<groupId>wsdl4j</groupId>
	<artifactId>wsdl4j</artifactId>
</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'
	jaxb("org.glassfish.jaxb:jaxb-xjc")
	testImplementation('org.springframework.boot:spring-boot-starter-test')
}

创建 XML Schema 来定义域

Web 服务域在 XML Schema 文件 (XSD) 中定义,Spring-WS 将自动将其导出为 WSDL。

创建一个 XSD 文件,其中包含返回国家/地区的 namepopulationcapitalcurrency 的操作。以下列表(来自 src/main/resources/countries.xsd)展示了所需的 XSD 文件

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="https://springframework.org.cn/guides/gs-producing-web-service"
           targetNamespace="https://springframework.org.cn/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>

基于 XML Schema 生成域类

下一步是从 XSD 文件生成 Java 类。正确的方法是在构建时使用 Maven 或 Gradle 插件自动执行此操作。

以下列表展示了 Maven 所需的插件配置

<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>${project.basedir}/src/main/resources/countries.xsd</source>
		</sources>
	</configuration>
</plugin>

生成的类放置在 target/generated-sources/jaxb/ 目录中。

要使用 Gradle 做同样的事情,您首先需要在构建文件中配置 JAXB,如下列表所示

configurations {
	jaxb
}

bootJar {
	archiveBaseName = 'gs-producing-web-service'
	archiveVersion =  '0.1.0'
}
构建文件包含 tagend 注释。这些标签使得更容易将其中一部分提取到本指南中进行更详细的解释。您自己的构建文件中不需要这些注释。

下一步是添加 genJaxb 任务,Gradle 使用该任务生成 Java 类。我们需要配置 Gradle 以在 build/generated-sources/jaxb 中找到这些生成的 Java 类,并将 genJaxb 添加为 compileJava 任务的依赖项。以下列表展示了必要的添加内容

sourceSets {
	main {
		java {
			srcDir 'src/main/java'
			srcDir 'build/generated-sources/jaxb'
		}
	}
}

task genJaxb {
	ext.sourcesDir = "${buildDir}/generated-sources/jaxb"
	ext.schema = "src/main/resources/countries.xsd"

	outputs.dir sourcesDir

	doLast() {
		project.ant {
			taskdef name: "xjc", classname: "com.sun.tools.xjc.XJCTask",
					classpath: configurations.jaxb.asPath
			mkdir(dir: sourcesDir)

			xjc(destdir: sourcesDir, schema: schema) {
				arg(value: "-wsdl")
				produces(dir: sourcesDir, includes: "**/*.java")
			}
		}
	}
}

compileJava.dependsOn genJaxb

由于 Gradle 还没有 JAXB 插件,因此它涉及一个 Ant 任务,这使得它比 Maven 中稍微复杂一些。

在这两种情况下,JAXB 域对象生成过程都已集成到构建工具的生命周期中,因此无需额外的运行步骤。

创建国家/地区仓库 (Repository)

为了向 Web 服务提供数据,请创建一个国家/地区仓库。在本指南中,您将创建一个带有硬编码数据的虚拟国家/地区仓库实现。以下列表(来自 src/main/java/com/example/producingwebservice/CountryRepository.java)展示了如何实现

package com.example.producingwebservice;

import jakarta.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);
	}
}

创建国家/地区服务端点 (Endpoint)

要创建服务终点,您只需要一个带有少量 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 = "https://springframework.org.cn/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 注解根据消息的 namespacelocalPart 选择处理方法。

@RequestPayload 注解表明传入消息将映射到方法的 request 参数。

@ResponsePayload 注解使 Spring WS 将返回值映射到响应有效载荷。

在所有这些代码片段中,除非您已运行基于 WSDL 生成域类的任务,否则您的 IDE 中会报告 io.spring.guides 类的编译时错误。

配置 Web 服务 Beans

创建一个包含 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> 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("https://springframework.org.cn/guides/gs-producing-web-service");
		wsdl11Definition.setSchema(countriesSchema);
		return wsdl11Definition;
	}

	@Bean
	public XsdSchema countriesSchema() {
		return new SimpleXsdSchema(new ClassPathResource("countries.xsd"));
	}
}
您需要为 MessageDispatcherServletDefaultWsdl11Definition 指定 bean 名称。bean 名称决定了 Web 服务和生成的 WSDL 文件可用的 URL。在此示例中,WSDL 将在 http://<host>:<port>/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。例如,如果类路径中有 spring-webmvc,此注解会将应用程序标记为 Web 应用程序并激活关键行为,例如设置 DispatcherServlet

  • @ComponentScan:告诉 Spring 在 com/example 包中查找其他组件、配置和服务,以便找到控制器。

main() 方法使用 Spring Boot 的 SpringApplication.run() 方法来启动应用程序。您注意到没有一行 XML 吗?也没有 web.xml 文件。这个 Web 应用程序是 100% 纯 Java,您无需处理任何底层配置或基础设施。

构建可执行 JAR

您可以使用 Gradle 或 Maven 从命令行运行应用程序。您还可以构建一个包含所有必需依赖项、类和资源的单一可执行 JAR 文件并运行它。构建可执行 JAR 可以轻松地在整个开发生命周期中、跨不同环境等情况下将服务作为应用程序进行交付、版本控制和部署。

如果您使用 Gradle,可以使用 ./gradlew bootRun 运行应用程序。或者,您可以使用 ./gradlew build 构建 JAR 文件,然后按如下方式运行 JAR 文件

java -jar build/libs/gs-soap-service-0.1.0.jar

如果您使用 Maven,可以使用 ./mvnw spring-boot:run 运行应用程序。或者,您可以使用 ./mvnw clean package 构建 JAR 文件,然后按如下方式运行 JAR 文件

java -jar target/gs-soap-service-0.1.0.jar
此处描述的步骤会创建一个可运行的 JAR 文件。您也可以构建一个经典的 WAR 文件

将显示日志输出。服务应在几秒钟内启动并运行。

测试应用程序

应用程序运行后,您可以对其进行测试。创建一个名为 request.xml 的文件,其中包含以下 SOAP 请求

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
				  xmlns:gs="https://springframework.org.cn/guides/gs-producing-web-service">
   <soapenv:Header/>
   <soapenv:Body>
      <gs:getCountryRequest>
         <gs:name>Spain</gs: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 \
  > target/response.xml && xmllint --format target/response.xml

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                                  xmlns:gs="https://springframework.org.cn/guides/gs-producing-web-service">
   <soapenv:Header/>
   <soapenv:Body>
      <gs:getCountryRequest>
         <gs:name>Spain</gs:name>
      </gs:getCountryRequest>
   </soapenv:Body>
</soapenv:Envelope>

EOF

结果您应该看到以下响应

<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header/>
  <SOAP-ENV:Body>
    <ns2:getCountryResponse xmlns:ns2="https://springframework.org.cn/guides/gs-producing-web-service">
      <ns2:country>
        <ns2:name>Spain</ns2:name>
        <ns2:population>46704314</ns2:population>
        <ns2:capital>Madrid</ns2:capital>
        <ns2:currency>EUR</ns2: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 查看格式良好的结果。

总结

恭喜!您已经使用 Spring Web Services 开发了基于 SOAP 的服务。

另请参阅

以下指南也可能有所帮助

想撰写新指南或为现有指南贡献?查看我们的贡献指南

所有指南的代码均以 ASLv2 许可证发布,文字内容以 Attribution, NoDerivatives creative commons license 发布。

获取代码