使用 Spring 消费 SOAP 网络服务

本指南将引导您完成使用 Spring 消费基于 SOAP 的网络服务的流程。

您将构建什么

您将构建一个客户端,使用 SOAP 从远程基于 WSDL 的网络服务获取国家数据。您可以按照 本指南 了解更多关于国家服务的信息,并自行运行该服务。

该服务提供国家数据。您可以根据国家名称查询有关该国家的数据。

您需要什么

如何完成本指南

与大多数 Spring 入门指南 一样,您可以从头开始并完成每个步骤,也可以跳过您已经熟悉的基本设置步骤。无论哪种方式,您最终都会得到可运行的代码。

从头开始,请继续执行 使用 Spring Initializr 开始

跳过基础步骤,请执行以下操作

完成时,您可以将您的结果与 gs-consuming-web-service/complete 中的代码进行比较。

在本地运行目标 Web 服务

按照 配套指南 中的步骤操作,或克隆 存储库 并运行该服务(例如,使用 mvn spring-boot:run), 从其 complete 目录运行。您可以通过在浏览器中访问 https://127.0.0.1:8080/ws/countries.wsdl 来验证其是否正常工作。如果您不这样做,稍后您将在构建中看到 JAXB 工具的令人困惑的异常。

使用 Spring Initializr 开始

对于所有 Spring 应用程序,您都应该从 Spring Initializr 开始。Initializr 提供了一种快速的方法来引入应用程序所需的所有依赖项,并为您完成许多设置工作。此示例只需要 Spring Web Services 依赖项。

您可以使用此 预初始化项目 并单击“生成”以下载 ZIP 文件。此项目配置为适合本教程中的示例。

初始化项目

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

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

  3. 单击依赖项并选择Spring Web Services

  4. 单击生成

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

如果您的 IDE 集成了 Spring Initializr,您可以从 IDE 中完成此过程。
您也可以从 Github 分叉项目并在您的 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>https://127.0.0.1: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=8081application.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: "https://127.0.0.1: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 网络服务的接口在 WSDL 中捕获。JAXB 提供了一种从 WSDL(更确切地说是 WSDL 的 <Types/> 部分中包含的 XSD)生成 Java 类的方法。您可以在 https://127.0.0.1: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>https://127.0.0.1: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: "https://127.0.0.1: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("https://127.0.0.1:8080/ws/countries", request,
            new SoapActionCallback(
                "https://springframework.org.cn/guides/gs-producing-web-service/GetCountryRequest"));

    return response;
  }

}

客户端包含一个方法 (getCountry),该方法执行实际的 SOAP 交换。

在此方法中,GetCountryRequestGetCountryResponse 类都源自 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("https://127.0.0.1:8080/ws");
    client.setMarshaller(marshaller);
    client.setUnmarshaller(marshaller);
    return client;
  }

}

marshaller 指向生成的领域对象集合,并将使用它们在 XML 和 POJO 之间进行序列化和反序列化。

countryClient 使用前面显示的国家服务的 URI 进行创建和配置。它还配置为使用 JAXB 序列化程序。

运行应用程序

此应用程序打包后可以从控制台运行并检索给定国家名称的数据,如下面的清单(来自 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 应用程序上下文中的组件进行管理。

此应用程序被硬编码为查找“西班牙”。在本指南的后面,您将看到如何在不编辑代码的情况下输入不同的符号。

构建可执行 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 服务。

另请参阅

以下指南可能也有帮助

想编写新的指南或为现有指南做出贡献?请查看我们的 贡献指南

所有指南均已获得代码的 ASLv2 许可证以及文本的 署名-非衍生作品创作共用许可证

获取代码