翻譯-使用Spring呼叫SOAP Web Service

黃博文發表於2014-09-10

原文連結: http://spring.io/guides/gs/consuming-web-service/

呼叫SOAP web service

本指南將指導你使用Spring呼叫一個基於SOAP的web service的整個過程。

指南內容

你將構建一個客戶端,使用SOAP用來從遠端的基於WSDL的web service獲取天氣資料。請訪問http://wiki.cdyne.com/index.php/CDYNE_Weather進一步獲取該天氣服務的資訊。

該服務根據郵編返回天氣預測。你可以使用自己的郵編。

準備事項

  • 大約15分鐘

  • 鍾愛的編輯器或IDE

  • JDK1.6或更高版本

  • Gradle 1.11+ 或 Maven 3.0+

  • 你也可以直接參閱該指南匯入程式碼,或通過Spring工具集(Spring Tool Suite,簡稱STS)通過網頁瀏覽程式碼,從而幫助你學習該章節內容。原始碼下載地址:https://github.com/spring-guides/gs-consuming-web-service.git

如何完成該指南

如同大多數的示例教程一樣,你可以從頭開始並完成每個步驟,或者你也可以跳過已經熟悉的基礎章節。無論怎樣,最終你要得到可以工作的程式碼。

從頭開始,請移動到使用Gradle構建章節。

跳過基礎部分,請做以下事情:

  • 下載並解壓該向導的原始碼,或者使用Git複製一份: git clone https://github.com/spring-guides/gs-consuming-web-service.git

  • 切換到gs-consuming-web-service/initial

  • 跳到基於WSDL生成領域物件章節。

當完成後,你可以使用gs-consuming-web-service/complete目錄中的程式碼檢查你的結果。

使用Gradle構建

首先你要設定一個基本的build指令碼。當構建Spring應用程式時,你可以使用任何構建系統,但是這裡只包括了使用MavenGradle的程式碼。如果你兩者都不熟悉,請訪問使用Gradle構建Java專案使用Maven構建Java專案

建立目錄結構

在你選擇的存放專案的目錄中,建立如下的子目錄結構。例如,在*nix系統中使用mkdir -p src/main/java/hello

1
2
3
4
└── src
    └── main
        └── java
            └── hello

建立Gradle 構建檔案

下面是一個初始的Gradle build檔案

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
configurations {
    jaxb
}

buildscript {
    repositories {
        maven { url "http://repo.spring.io/libs-release" }
        mavenLocal()
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.1.6.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

repositories {
    mavenLocal()
    mavenCentral()
    maven { url 'http://repo.spring.io/libs-release' }
}

// tag::wsdl[]
task genJaxb {
    ext.sourcesDir = "${buildDir}/generated-sources/jaxb"
    ext.classesDir = "${buildDir}/classes/jaxb"
    ext.schema = "http://wsf.cdyne.com/WeatherWS/Weather.asmx?wsdl"

    outputs.dir classesDir

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

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

            javac(destdir: classesDir, source: 1.6, target: 1.6, debug: true,
                    debugLevel: "lines,vars,source",
                    classpath: configurations.jaxb.asPath) {
                src(path: sourcesDir)
                include(name: "**/*.java")
                include(name: "*.java")
            }

            copy(todir: classesDir) {
                fileset(dir: sourcesDir, erroronmissingdir: false) {
                    exclude(name: "**/*.java")
                }
            }
        }
    }
}
// end::wsdl[]

dependencies {
    compile("org.springframework.boot:spring-boot-starter")
    compile("org.springframework.ws:spring-ws-core")
    compile(files(genJaxb.classesDir).builtBy(genJaxb))

    jaxb "com.sun.xml.bind:jaxb-xjc:2.1.7"
}

jar {
    from genJaxb.classesDir
}

task wrapper(type: Wrapper) {
    gradleVersion = '1.11'
}

task afterEclipseImport {
    dependsOn genJaxb
}

Spring Boot gradle外掛提供了很多便利的特性:

  • 將classpath中的所有jar包構建單個可執行的jar包,從而更容易執行和傳播服務。

  • 搜尋public static void main()方法並標記為可執行的類。

  • 提供了一個內建的依賴管理器,設定依賴版本以匹配Spring Boot依賴。你可以覆蓋為任何你希望的版本,但預設會使用Boot選擇的版本。

使用Maven構建

首先你需要設定一個基本的構建指令碼。你可以使用任何構建系統來構建Spring應用程式,但這裡包含了Maven的程式碼。如果你對Maven不熟,請訪問使用Maven構建Java專案

建立目錄結構

在你選擇的存放專案的目錄中,建立如下的子目錄結構。例如,在*nix系統中使用mkdir -p src/main/java/hello

1
2
3
4
└── src
    └── main
        └── java
            └── hello
pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.springframework</groupId>
    <artifactId>gs-consuming-web-service</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.1.6.RELEASE</version>
    </parent>

    <properties>
        <!-- use UTF-8 for everything -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ws</groupId>
            <artifactId>spring-ws-core</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!-- tag::wsdl[] -->
            <plugin>
                <groupId>org.jvnet.jaxb2.maven2</groupId>
                <artifactId>maven-jaxb2-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <schemaLanguage>WSDL</schemaLanguage>
                    <generatePackage>hello.wsdl</generatePackage>
                    <forceRegenerate>true</forceRegenerate>
                    <schemas>
                        <schema>
                            <url>http://wsf.cdyne.com/WeatherWS/Weather.asmx?wsdl</url>
                        </schema>
                    </schemas>
                </configuration>
            </plugin>
            <!-- end::wsdl[] -->
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-releases</id>
            <name>Spring Releases</name>
            <url>http://repo.spring.io/libs-release</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-releases</id>
            <url>http://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>

</project>

注意:你可能注意到我們指定了maven-complier-plugin的版本。通常並不推薦這樣做。這裡主要是為了解決我們的CI系統預設執行在該外掛的早期版本(java5之前)的一個問題。

Spring Boot Maven外掛提供了很多便利的特性:

  • 將classpath中的所有jar包構建單個可執行的jar包,從而更容易執行和傳播服務。

  • 搜尋public static void main()方法並標記為可執行的類。

  • 提供了一個內建的依賴管理器,設定依賴版本以匹配Spring Boot依賴。你可以覆蓋為任何你希望的版本,但預設會使用Boot選擇的版本。

使用Spring工具集構建

如果你擁有Spring工具集,只需簡單的直接匯入該指南

注意:如果你閱讀過生成SOAP web service,你可能會疑惑為什麼該指南沒有使用spring-boot-starter-ws?這是因為Spring Boot Starter只用於伺服器端程式。Starter提供了諸如嵌入式Tomcat等功能,而服務呼叫則不需要這些。

基於WSDL生成領域物件

SOAP web service的介面描述在WSDL檔案中。JAXB提供了一個簡單的方式來從WSDL(或者WSDL中包含在<Types/>節點中的XSD)生成Java類。可以訪問http://wsf.cdyne.com/WeatherWS/Weather.asmx?wsdl獲取該天氣服務的WSDL。

你需要下列外掛來使用maven從WSDL生成Java類:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<plugin>
    <groupId>org.jvnet.jaxb2.maven2</groupId>
    <artifactId>maven-jaxb2-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <schemaLanguage>WSDL</schemaLanguage>
        <generatePackage>hello.wsdl</generatePackage>
        <forceRegenerate>true</forceRegenerate>
        <schemas>
            <schema>
                <url>http://wsf.cdyne.com/WeatherWS/Weather.asmx?wsdl</url>
            </schema>
        </schemas>
    </configuration>
</plugin>

該程式碼將通過指定的WSDL的URL生成類,並放置在hello.wsdl包中。

你也可以使用下列程式碼在Gradle中完成同樣的事:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
task genJaxb {
    ext.sourcesDir = "${buildDir}/generated-sources/jaxb"
    ext.classesDir = "${buildDir}/classes/jaxb"
    ext.schema = "http://wsf.cdyne.com/WeatherWS/Weather.asmx?wsdl"

    outputs.dir classesDir

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

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

            javac(destdir: classesDir, source: 1.6, target: 1.6, debug: true,
                    debugLevel: "lines,vars,source",
                    classpath: configurations.jaxb.asPath) {
                src(path: sourcesDir)
                include(name: "**/*.java")
                include(name: "*.java")
            }

            copy(todir: classesDir) {
                fileset(dir: sourcesDir, erroronmissingdir: false) {
                    exclude(name: "**/*.java")
                }
            }
        }
    }
}

由於gradle還沒有jaxb外掛,所以它呼叫了一個ant任務,程式碼看起來比maven稍顯複雜。 在maven和gradle兩個示例中,JAXB領域物件生成過程被包括在構建工具的生命週期中,所以無需額外步驟來執行。

建立天氣服務客戶端

建立一個web service客戶端,你只需要擴充套件WebServiceGatewaySupport類並編寫操作程式碼:

src/main/java/hello/WeatherClient.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package hello;

import java.text.SimpleDateFormat;

import org.springframework.ws.client.core.support.WebServiceGatewaySupport;
import org.springframework.ws.soap.client.core.SoapActionCallback;

import hello.wsdl.Forecast;
import hello.wsdl.ForecastReturn;
import hello.wsdl.GetCityForecastByZIP;
import hello.wsdl.GetCityForecastByZIPResponse;
import hello.wsdl.Temp;

public class WeatherClient extends WebServiceGatewaySupport {

    public GetCityForecastByZIPResponse getCityForecastByZip(String zipCode) {
        GetCityForecastByZIP request = new GetCityForecastByZIP();
        request.setZIP(zipCode);

        System.out.println();
        System.out.println("Requesting forecast for " + zipCode);

        GetCityForecastByZIPResponse response = (GetCityForecastByZIPResponse) getWebServiceTemplate().marshalSendAndReceive(
                request,
                new SoapActionCallback(
                        "http://ws.cdyne.com/WeatherWS/GetCityForecastByZIP"));

        return response;
    }

    public void printResponse(GetCityForecastByZIPResponse response) {
        ForecastReturn forecastReturn = response.getGetCityForecastByZIPResult();

        if (forecastReturn.isSuccess()) {
            System.out.println();
            System.out.println("Forecast for " + forecastReturn.getCity() + ", "
                    + forecastReturn.getState());

            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            for (Forecast forecast : forecastReturn.getForecastResult().getForecast()) {
                System.out.print(format.format(forecast.getDate().toGregorianCalendar().getTime()));
                System.out.print(" ");
                System.out.print(forecast.getDesciption());
                System.out.print(" ");
                Temp temperature = forecast.getTemperatures();
                System.out.print(temperature.getMorningLow() + "\u00b0-"
                        + temperature.getDaytimeHigh() + "\u00b0 ");
                System.out.println();
            }
        } else {
            System.out.println("No forecast received");
        }
    }

}

該客戶端包含了兩個方法。getCityForecastByZip用於實際的SOAP交換;printResponse列印收到的響應結果。我們重點關注第一個方法。

在該方法中,GetCityForecastByZIPGetCityForecastByZIPResponse類衍生於WSDL中,被前一個步驟描述過的JAXB生成。該方法建立了GetCityForecastByZIP請求物件並設定了zipCode引數。列印出郵編後,使用WebServiceGatewaySupport基類提供的WebServiceTemplate來進行實際的SOAP交換。它傳入GetCityForecastByZIP請求物件,以及一個SoapActionCallback來傳入SOAPAction頭,因為WSDL說明其需要在<soap:operation/>元素中使用該頭。該方法將返回值轉換為GetCityForecastByZIPResponse物件,然後返回該物件。

配置web service元件

Spring WS使用了Spring框架的OXM模組。該模組擁有Jaxb2Marshaller類來序列化和反序列化XML請求。

src/main/java/hello/WeatherConfiguration.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package hello;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;

@Configuration
public class WeatherConfiguration {

    @Bean
    public Jaxb2Marshaller marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setContextPath("hello.wsdl");
        return marshaller;
    }

    @Bean
    public WeatherClient weatherClient(Jaxb2Marshaller marshaller) {
        WeatherClient client = new WeatherClient();
        client.setDefaultUri("http://wsf.cdyne.com/WeatherWS/Weather.asmx");
        client.setMarshaller(marshaller);
        client.setUnmarshaller(marshaller);
        return client;
    }

}

marshaller被指向了生成的領域物件集合,將使用這些物件來實現XML和POJO之間的序列化和反序列化。

我們使用了上面顯示的天氣服務URI建立和配置了weatherClient。他也被配置使用JAXB marshaller。

生成可執行的應用程式

該應用程式被打包後可執行與命令列,傳入一個郵編則會得到一個天氣預報。

src/main/java/hello/Application.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;

import hello.wsdl.GetCityForecastByZIPResponse;

public class Application {

    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(WeatherConfiguration.class, args);

        WeatherClient weatherClient = ctx.getBean(WeatherClient.class);

        String zipCode = "94304";
        if (args.length > 0) {
            zipCode = args[0];
        }
        GetCityForecastByZIPResponse response = weatherClient.getCityForecastByZip(zipCode);
        weatherClient.printResponse(response);
    }

}

main()方法呼叫了SpringApplication輔助方法,並向其run()方法傳入了WeatherConfiguration.class引數。這會使Spring從WeatherConfiguration中讀取註解後設資料,並作為Spring應用程式上下文中的一個元件進行管理。

注意:該應用程式硬編碼了郵編94304,Palo Alto, CA。在該指南的最後,你可以看到如何新增不同的郵編而不用修改程式碼。

構建可執行的jar包

你可以建立一個包含所有必須的依賴,類,及資源的可執行的JAR檔案。這很方便傳輸,版本管理以及獨立於部署生命週期來部署服務,跨不同的環境,諸如此類。

1
gradlew build

然後你可以執行WAR檔案:

1
java -jar build/libs/gs-consuming-web-service-0.1.0.jar

如果你使用的是maven,你可以使用mvn spring-boot:run來執行程式,或者你可以使用mvn clean package構建JAR檔案,並使用下面命令來執行:

1
java -jar target/gs-consuming-web-service-0.1.0.jar

注意:上面的產出物是一個可執行JAR檔案。你也可以建立一個經典的WAR檔案。 

執行服務

如果使用的是Gradle,可以使用以下命令來執行服務:

1
gradlew clean build && java -jar build/libs/gs-consuming-web-service-0.1.0.jar

注意:如果你使用的是Maven,可以使用以下命令來執行服務:mvn clean package && java -jar target/gs-consuming-web-service-0.1.0.jar

你也可以通過Gradle直接執行該程式:

1
gradlew bootRun

注意:使用mvn的話,命令是mvn spring-boot:run

可以看到日誌輸出。該服務應該在幾秒鐘內啟動並執行起來。

1
Requesting forecast for94304ForecastforPaloAlto, CA2013-01-03PartlyCloudy°-57°2013-01-04PartlyCloudy41°-58°2013-01-05PartlyCloudy41°-59°2013-01-06PartlyCloudy44°-56°2013-01-07PartlyCloudy41°-60°2013-01-08PartlyCloudy42°-60°2013-01-09PartlyCloudy43°-58°

你也可以使用不同的郵編:java -jar build/libs/gs-consuming-web-service-0.1.0.jar 34769

1
Requesting forecast for34769ForecastforSaintCloud, FL2014-02-18Sunny51°-79°2014-02-19Sunny55°-81°2014-02-20Sunny59°-84°2014-02-21PartlyCloudy63°-85°2014-02-22PartlyCloudy63°-84°2014-02-23PartlyCloudy63°-82°2014-02-24PartlyCloudy62°-80°

總結

恭喜你!你開發了一個客戶端來使用Spring呼叫一個基於SOAP的web service。

相關文章