Spring Boot 与 Docker
观察 GraphQL 的实际运行

本指南将引导您使用 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,您需要添加一个依赖项、一个配置文件和一个 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 交换。

在这个方法中,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 服务。

另请参阅

以下指南可能也会有所帮助:

本页目录