本指南将引导您使用 Spring 消费基于 SOAP 的 Web 服务的过程。
您将构建什么
您将构建一个客户端,利用 SOAP 从基于 WSDL 的远程 Web 服务中获取国家数据。您可以通过查看 此指南 了解更多关于该国家服务的信息,并自行运行该服务。
该服务提供国家数据。您将能够根据国家名称查询相关数据。
您需要什么
-
大约 15 分钟
-
喜欢的文本编辑器或集成开发环境
-
Java 17 或更高版本
-
您还可以直接将代码导入您的 IDE 中:
如何完成本指南
与大多数 Spring 入门指南 一样,您可以从头开始并完成每个步骤,也可以跳过您已经熟悉的基本设置步骤。无论哪种方式,您最终都能得到可运行的代码。
要从头开始,请继续阅读 从 Spring Initializr 开始。
要跳过基础知识,请执行以下操作:
- 下载并解压缩本指南的源代码仓库,或使用 Git 克隆它:
git clone https://github.com/spring-guides/gs-consuming-web-service.git
- 进入
gs-consuming-web-service/initial
目录 - 直接跳转到 基于 WSDL 生成领域对象。
完成后,您可以将您的结果与 gs-consuming-web-service/complete
中的代码进行对比。
在本地运行目标 Web 服务
按照配套指南中的步骤操作,或者克隆仓库并从其complete
目录运行服务(例如,使用mvn spring-boot:run
)。您可以通过在浏览器中访问http://localhost:8080/ws/countries.wsdl
来验证其是否正常工作。如果不这样做,稍后在构建过程中您会看到来自JAXB工具的令人困惑的异常。
使用 Spring Initializr 开始
对于所有的 Spring 应用程序,您应该从 Spring Initializr 开始。Initializr 提供了一种快速的方式来引入应用程序所需的所有依赖项,并为您完成了大量的设置工作。本示例仅需要 Spring Web Services 依赖项。
您可以使用这个 预先初始化的项目,然后点击 Generate 下载一个 ZIP 文件。该项目已配置为与本教程中的示例相匹配。
要初始化项目:
-
访问 https://start.spring.io。该服务会拉取应用程序所需的所有依赖项,并为您完成大部分设置工作。
-
选择 Gradle 或 Maven 以及您想要使用的语言。本指南假设您选择了 Java。
-
点击 Dependencies 并选择 Spring Web Services。
-
点击 Generate。
-
下载生成的 ZIP 文件,这是一个根据您的选择配置的 Web 应用程序存档。
如果您的 IDE 集成了 Spring Initializr,您可以直接在 IDE 中完成此过程。
你也可以从 Github 上 fork 这个项目,然后在你的 IDE 或其他编辑器中打开它。
修改构建文件
Spring Initializr 生成的构建文件需要根据本指南进行大量修改。此外,对 pom.xml
(用于 Maven)和 build.gradle
(用于 Gradle)的修改也有很大不同。
Maven
对于 Maven,您需要添加一个依赖项、一个配置文件和一个 WSDL 生成插件。
以下清单显示了您需要在 Maven 中添加的依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
基于 WSDL 生成域对象 部分描述了 WSDL 生成插件。
以下清单展示了最终的 pom.xml
文件:
<?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>3.3.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>consuming-web-service-complete</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>consuming-web-service-complete</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- tag::dependency[] -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- end::dependency[] -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- tag::wsdl[] -->
<plugin>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<goals>
<goal>wsimport</goal>
</goals>
</execution>
</executions>
<configuration>
<packageName>com.example.consumingwebservice.wsdl</packageName>
<wsdlUrls>
<wsdlUrl>http://localhost:8080/ws/countries.wsdl</wsdlUrl>
</wsdlUrls>
<sourceDestDir>${sourcesDir}</sourceDestDir>
<destDir>${classesDir}</destDir>
<extension>true</extension>
</configuration>
</plugin>
<!-- end::wsdl[] -->
</plugins>
</build>
</project>
Gradle
对于 Gradle,您需要添加一个依赖项、一个配置、一个 bootJar
部分以及一个 WSDL 生成插件。
以下清单显示了您需要在 Gradle 中添加的依赖项:
implementation ('org.springframework.boot:spring-boot-starter-web-services') {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
jaxws 'com.sun.xml.ws:jaxws-tools:3.0.0',
'jakarta.xml.ws:jakarta.xml.ws-api:3.0.0',
'jakarta.xml.bind:jakarta.xml.bind-api:3.0.0',
'jakarta.activation:jakarta.activation-api:2.0.0',
'com.sun.xml.ws:jaxws-rt:3.0.0'
请注意 Tomcat 的排除。如果在此构建中允许运行 Tomcat,您将与提供国家数据的 Tomcat 实例发生端口冲突。
由于端口冲突,初始项目无法启动。您可以通过添加一个包含
server.port=8081
属性的application.properties
文件来解决此问题。由于初始项目旨在作为起点,您可以跳过尝试使其运行的部分。
基于 WSDL 生成领域对象 部分描述了 WSDL 生成插件。
以下清单展示了最终的 build.gradle
文件:
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.0'
id 'io.spring.dependency-management' version '1.1.5'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
ext.jaxwsSourceDir = "${buildDir}/generated/sources/jaxws"
// tag::configurations[]
configurations {
jaxws
}
// end::configurations[]
repositories {
mavenCentral()
}
// tag::wsdl[]
task wsimport {
description = 'Generate classes from wsdl using wsimport'
doLast {
project.mkdir(jaxwsSourceDir)
ant {
taskdef(name: 'wsimport',
classname: 'com.sun.tools.ws.ant.WsImport',
classpath: configurations.jaxws.asPath
)
wsimport(
keep: true,
destdir: jaxwsSourceDir,
extension: "true",
verbose: true,
wsdl: "http://localhost:8080/ws/countries.wsdl",
xnocompile: true,
package: "com.example.consumingwebservice.wsdl") {
xjcarg(value: "-XautoNameResolution")
}
}
}
}
sourceSets {
main {
java.srcDirs += jaxwsSourceDir
}
}
compileJava {
dependsOn wsimport
}
// end::wsdl[]
dependencies {
// tag::dependency[]
implementation ('org.springframework.boot:spring-boot-starter-web-services') {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
jaxws 'com.sun.xml.ws:jaxws-tools:3.0.0',
'jakarta.xml.ws:jakarta.xml.ws-api:3.0.0',
'jakarta.xml.bind:jakarta.xml.bind-api:3.0.0',
'jakarta.activation:jakarta.activation-api:2.0.0',
'com.sun.xml.ws:jaxws-rt:3.0.0'
// end::dependency[]
testImplementation('org.springframework.boot:spring-boot-starter-test')
}
tasks.named('test') {
useJUnitPlatform()
}
基于 WSDL 生成领域对象
SOAP Web 服务的接口在 WSDL 中定义。JAXB 提供了一种从 WSDL(更准确地说,是从 WSDL 的 <Types/>
部分包含的 XSD)生成 Java 类的方式。您可以在 http://localhost:8080/ws/countries.wsdl
找到国家服务的 WSDL。
要在 Maven 中从 WSDL 生成 Java 类,您需要以下插件配置:
<plugin>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<goals>
<goal>wsimport</goal>
</goals>
</execution>
</executions>
<configuration>
<packageName>com.example.consumingwebservice.wsdl</packageName>
<wsdlUrls>
<wsdlUrl>http://localhost:8080/ws/countries.wsdl</wsdlUrl>
</wsdlUrls>
<sourceDestDir>${sourcesDir}</sourceDestDir>
<destDir>${classesDir}</destDir>
<extension>true</extension>
</configuration>
</plugin>
此配置会为指定URL找到的WSDL生成类,并将这些类放入com.example.consumingwebservice.wsdl
包中。要生成该代码,请运行./mvnw compile
,然后查看target/generated-sources
目录以验证是否成功。
要在Gradle中实现相同功能,您需要在构建文件中添加以下内容:
task wsimport {
description = 'Generate classes from wsdl using wsimport'
doLast {
project.mkdir(jaxwsSourceDir)
ant {
taskdef(name: 'wsimport',
classname: 'com.sun.tools.ws.ant.WsImport',
classpath: configurations.jaxws.asPath
)
wsimport(
keep: true,
destdir: jaxwsSourceDir,
extension: "true",
verbose: true,
wsdl: "http://localhost:8080/ws/countries.wsdl",
xnocompile: true,
package: "com.example.consumingwebservice.wsdl") {
xjcarg(value: "-XautoNameResolution")
}
}
}
}
sourceSets {
main {
java.srcDirs += jaxwsSourceDir
}
}
compileJava {
dependsOn wsimport
}
由于 Gradle(目前)还没有 JAXB 插件,因此它需要使用 Ant 任务,这使得它比在 Maven 中稍微复杂一些。要生成该代码,请运行 ./gradlew compileJava
,然后查看 build/generated-sources
以确认其是否成功。
在 Maven 和 Gradle 中,JAXB 领域对象的生成过程已经集成到构建工具的生命周期中,因此一旦构建成功,您无需运行任何额外的步骤。
创建国家服务客户端
要创建一个Web服务客户端,您需要继承WebServiceGatewaySupport类并编写您的操作,如下例所示(来自src/main/java/com/example/consumingwebservice/CountryClient.java
):
package com.example.consumingwebservice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
import org.springframework.ws.soap.client.core.SoapActionCallback;
import com.example.consumingwebservice.wsdl.GetCountryRequest;
import com.example.consumingwebservice.wsdl.GetCountryResponse;
public class CountryClient extends WebServiceGatewaySupport {
private static final Logger log = LoggerFactory.getLogger(CountryClient.class);
public GetCountryResponse getCountry(String country) {
GetCountryRequest request = new GetCountryRequest();
request.setName(country);
log.info("Requesting location for " + country);
GetCountryResponse response = (GetCountryResponse) getWebServiceTemplate()
.marshalSendAndReceive("http://localhost:8080/ws/countries", request,
new SoapActionCallback(
"http://spring.io/guides/gs-producing-web-service/GetCountryRequest"));
return response;
}
}
客户端包含一个方法(getCountry
),该方法负责实际的 SOAP 交换。
在这个方法中,GetCountryRequest
和 GetCountryResponse
类都是从 WSDL 派生而来,并在 JAXB 生成过程中生成(详见基于 WSDL 生成领域对象)。它创建了 GetCountryRequest
请求对象,并使用 country
参数(国家名称)进行设置。在打印出国家名称后,它使用 WebServiceGatewaySupport
基类提供的 WebServiceTemplate 来执行实际的 SOAP 交换。它将 GetCountryRequest
请求对象(以及一个 SoapActionCallback
来传递带有请求的 SOAPAction 头信息)作为参数传递,因为 WSDL 在 <soap:operation/>
元素中描述了需要此头信息。它将响应转换为 GetCountryResponse
对象,然后返回该对象。
配置 Web 服务组件
Spring WS 使用 Spring Framework 的 OXM 模块,其中包含 Jaxb2Marshaller
来序列化和反序列化 XML 请求,如下例(来自 src/main/java/com/example/consumingwebservice/CountryConfiguration.java
)所示:
package com.example.consumingwebservice;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
@Configuration
public class CountryConfiguration {
@Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
// this package must match the package in the <generatePackage> specified in
// pom.xml
marshaller.setContextPath("com.example.consumingwebservice.wsdl");
return marshaller;
}
@Bean
public CountryClient countryClient(Jaxb2Marshaller marshaller) {
CountryClient client = new CountryClient();
client.setDefaultUri("http://localhost:8080/ws");
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
return client;
}
}
marshaller
指向生成的域对象集合,并将使用它们在 XML 和 POJO 之间进行序列化和反序列化。\ countryClient
被创建并配置为使用前面展示的国家服务的 URI。同时,它还配置为使用 JAXB marshaller。
运行应用程序
该应用程序已打包为可从控制台运行,并检索给定国家名称的数据,如下列代码(来自 src/main/java/com/example/consumingwebservice/ConsumingWebServiceApplication.java
)所示:
package com.example.consumingwebservice;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.example.consumingwebservice.wsdl.GetCountryResponse;
@SpringBootApplication
public class ConsumingWebServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumingWebServiceApplication.class, args);
}
@Bean
CommandLineRunner lookup(CountryClient countryClient) {
return args -> {
String country = "Spain";
if (args.length > 0) {
country = args[0];
}
GetCountryResponse response = countryClient.getCountry(country);
System.err.println(response.getCountry().getCurrency());
};
}
}
main()
方法委托给 SpringApplication 辅助类,将 CountryConfiguration.class
作为参数传递给它的 run()
方法。这指示 Spring 从 CountryConfiguration
读取注解元数据,并将其作为组件管理在 Spring 应用上下文中。
该应用程序被硬编码为查找“Spain”。在本指南的后面部分,您将看到如何在不修改代码的情况下输入不同的符号。
构建可执行的 JAR 文件
您可以使用 Gradle 或 Maven 从命令行运行应用程序。您还可以构建一个包含所有必要依赖项、类和资源的单一可执行 JAR 文件并运行它。构建可执行 JAR 文件使得在整个开发生命周期中、跨不同环境等场景下,轻松地打包、版本控制和部署该服务作为应用程序变得简单。
如果使用 Gradle,您可以通过 ./gradlew bootRun
来运行应用程序。或者,您可以使用 ./gradlew build
构建 JAR 文件,然后运行该 JAR 文件,如下所示:
java -jar build/libs/gs-consuming-web-service-0.1.0.jar
如果使用 Maven,您可以通过 ./mvnw spring-boot:run
来运行应用程序。或者,您可以使用 ./mvnw clean package
构建 JAR 文件,然后运行该 JAR 文件,如下所示:
java -jar target/gs-consuming-web-service-0.1.0.jar
这里描述的步骤会创建一个可运行的 JAR 文件。您也可以构建一个经典的 WAR 文件。
日志输出显示出来。服务应该会在几秒钟内启动并运行。
以下列表显示了初始响应:
Requesting country data for Spain
<getCountryRequest><name>Spain</name>...</getCountryRequest>
您可以通过运行以下命令来插入不同的国家:
java -jar build/libs/gs-consuming-web-service-0.1.0.jar Poland
然后响应变为如下:
Requesting location for Poland
<getCountryRequest><name>Poland</name>...</getCountryRequest>
总结
恭喜!您刚刚使用 Spring 开发了一个客户端来调用基于 SOAP 的 Web 服务。
另请参阅
以下指南可能也会有所帮助: