消费 SOAP Web 服务

本指南将引导您使用 Spring 消费基于 SOAP 的 Web 服务。

您将构建什么

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

该服务提供国家数据。您将能够根据国家名称查询有关国家的数据。

您需要什么

如何完成本指南

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

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

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

完成后,您可以对照 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 文件。此项目已配置好,适用于本教程中的示例。

初始化项目

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

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

  3. 点击 Dependencies 并选择 Spring Web Services

  4. 点击 Generate

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

如果您的 IDE 集成了 Spring Initializr,您可以直接在 IDE 中完成此过程。
您也可以从 Github fork 项目并在您的 IDE 或其他编辑器中打开它。

修改构建文件

Spring Initializr 创建的构建文件需要为本指南做相当多的修改。此外,对 pom.xml (Maven) 和 build.gradle (Gradle) 的修改差异很大。

Maven

对于 Maven,您需要添加一个依赖项、一个 profile 和一个 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(
                "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("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 服务的客户端。

另请参阅

以下指南可能也有帮助

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

所有指南的代码均采用 ASLv2 许可发布,文章内容采用 署名-禁止演绎 知识共享许可 发布。

获取代码