原文連結:http://spring.io/guides/gs/producing-web-service/
生成SOAP web service
該指南將帶領你使用Spring建立一個基於SOAP的web service的整個過程。
指南內容
你將建立一個服務,該服務通過一個基於WSDL的SOAP web service向外暴露歐洲國家的資料。
注意:為了簡化該示例,你將使用硬編碼方式嵌入英國,西班牙及波蘭。
準備事項
如何完成該指南
如同大多數的示例教程 一樣,你可以從頭開始並完成每個步驟,或者你也可以跳過已經熟悉的基礎章節。無論怎樣,最終你要得到可以工作的程式碼。
想從頭開始 ,請移動到使用Gradle構建 章節。
想跳過基礎部分 ,請做以下事情:
當完成後,你可以使用gs-soap-service/complete
目錄中的程式碼檢查你的結果。
首先你要設定一個基本的build指令碼。當構建Spring應用程式時,你可以使用任何構建系統,但是這裡只包括了使用Maven 和Gradle 的程式碼。如果你兩者都不熟悉,請訪問使用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
buildscript {
repositories {
mavenLocal ()
mavenCentral ()
maven { url "http://repo.spring.io/libs-release" }
}
dependencies {
classpath ( "org.springframework.boot:spring-boot-gradle-plugin:1.1.5.RELEASE" )
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
jar {
baseName = 'gs-producing-web-service'
version = '0.1.0'
}
repositories {
mavenLocal ()
mavenCentral ()
maven { url "http://repo.spring.io/libs-release" }
}
dependencies {
compile ( "org.springframework.boot:spring-boot-starter-web" )
}
task wrapper ( type: Wrapper ) {
gradleVersion = '1.11'
}
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
<?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-producting-web-service</artifactId>
<version> 0.1.0</version>
<parent>
<groupId> org.springframework.boot</groupId>
<artifactId> spring-boot-starter-parent</artifactId>
<version> 1.1.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId> org.springframework.boot</groupId>
<artifactId> spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<properties>
<!-- use UTF-8 for everything -->
<project.build.sourceEncoding> UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding> UTF-8</project.reporting.outputEncoding>
<start-class> hello.Application</start-class>
</properties>
<build>
<plugins>
<plugin>
<artifactId> maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId> org.springframework.boot</groupId>
<artifactId> spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
注意:你可能注意到我們指定了maven-complier-plugin 的版本。通常並不推薦 這樣做。這裡主要是為了解決我們的CI系統預設執行在該外掛的早期版本(java5之前)的一個問題。
Spring Boot Maven外掛 提供了很多便利的特性:
將classpath中的所有jar包構建單個可執行的jar包,從而更容易執行和傳播服務。
搜尋public static void main()
方法並標記為可執行的類。
提供了一個內建的依賴管理器,設定依賴版本以匹配Spring Boot依賴 。你可以覆蓋為任何你希望的版本,但預設會使用Boot選擇的版本。
使用Spring工具集構建
如果你擁有Spring工具集,只需簡單的直接匯入該指南 。
你建立的專案需要新增spring-ws-core
和wsdl4j依賴到構建檔案中。
maven程式碼:
1
2
3
4
5
6
7
8
9
<dependency>
<groupId> org.springframework.boot</groupId>
<artifactId> spring-boot-starter-ws</artifactId>
</dependency>
<dependency>
<groupId> wsdl4j</groupId>
<artifactId> wsdl4j</artifactId>
<version> 1.6.1</version>
</dependency>
gradle程式碼:
1
2
3
4
5
6
dependencies {
compile ( "org.springframework.boot:spring-boot-starter-ws" )
compile ( "wsdl4j:wsdl4j:1.6.1" )
jaxb ( "com.sun.xml.bind:jaxb-xjc:2.2.4-1" )
compile ( files ( genJaxb . classesDir ). builtBy ( genJaxb ))
}
建立XML格式來定義領域物件
該web service領域物件被定義在一個XML格式檔案中(XSD),Spring-WS將自動匯出為一個WSDL檔案。
建立一個XSD檔案,包含一個操作來返回一個國家的名稱 ,人口 ,首都 和貨幣 。
src/main/resources/countries.xsd
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
<xs:schema xmlns:xs= "http://www.w3.org/2001/XMLSchema" xmlns:tns= "http://spring.io/guides/gs-producing-web-service"
targetNamespace= "http://spring.io/guides/gs-producing-web-service" elementFormDefault= "qualified" >
<xs:element name= "getCountryRequest" >
<xs:complexType>
<xs:sequence>
<xs:element name= "name" type= "xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name= "getCountryResponse" >
<xs:complexType>
<xs:sequence>
<xs:element name= "country" type= "tns:country" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name= "country" >
<xs:sequence>
<xs:element name= "name" type= "xs:string" />
<xs:element name= "population" type= "xs:int" />
<xs:element name= "capital" type= "xs:string" />
<xs:element name= "currency" type= "tns:currency" />
</xs:sequence>
</xs:complexType>
<xs:simpleType name= "currency" >
<xs:restriction base= "xs:string" >
<xs:enumeration value= "GBP" />
<xs:enumeration value= "EUR" />
<xs:enumeration value= "PLN" />
</xs:restriction>
</xs:simpleType>
</xs:schema>
基於XML格式建立領域類
接下來的步驟是根據XSD檔案來建立java類。正確的方式是使用maven或gradle外掛在構建時間自動建立。
maven外掛配置;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<plugin>
<groupId> org.codehaus.mojo</groupId>
<artifactId> jaxb2-maven-plugin</artifactId>
<version> 1.6</version>
<executions>
<execution>
<id> xjc</id>
<goals>
<goal> xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<schemaDirectory> ${project.basedir}/src/main/resources/</schemaDirectory>
<outputDirectory> ${project.basedir}/src/main/java</outputDirectory>
<clearOutputDir> false</clearOutputDir>
</configuration>
</plugin>
生成的類放置在target/generated-sources/jaxb
目錄。
gradle外掛配置如下,首先需要在構建檔案中配置JAXB:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
configurations {
jaxb
}
jar {
baseName = 'gs-producing-web-service'
version = '0.1.0'
from genJaxb . classesDir
}
// tag::dependencies[]
dependencies {
compile ( "org.springframework.boot:spring-boot-starter-ws" )
compile ( "wsdl4j:wsdl4j:1.6.1" )
jaxb ( "com.sun.xml.bind:jaxb-xjc:2.2.4-1" )
compile ( files ( genJaxb . classesDir ). builtBy ( genJaxb ))
}
// end::dependencies[]
注意:上面的構建檔案擁有tag及end註釋。目的是為了能夠在本指南中更容易抽取出來並做進一步解釋。在你的構建檔案中無需這些註釋。
接下來的步驟是新增任務genJaxb
,該任務會生成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
task genJaxb {
ext . sourcesDir = "${buildDir}/generated-sources/jaxb"
ext . classesDir = "${buildDir}/classes/jaxb"
ext . schema = "src/main/resources/countries.xsd"
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 ) {
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提供資料,需要建立一個國家倉庫,在本指南中建立了一個硬編碼的偽造的國家倉庫實現。
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
package hello ;
import javax.annotation.PostConstruct ;
import java.util.ArrayList ;
import java.util.List ;
import io.spring.guides.gs_producing_web_service.Country ;
import io.spring.guides.gs_producing_web_service.Currency ;
import org.springframework.stereotype.Component ;
import org.springframework.util.Assert ;
@Component
public class CountryRepository {
private static final List < Country > countries = new ArrayList < Country >();
@PostConstruct
public void initData () {
Country spain = new Country ();
spain . setName ( "Spain" );
spain . setCapital ( "Madrid" );
spain . setCurrency ( Currency . EUR );
spain . setPopulation ( 46704314 );
countries . add ( spain );
Country poland = new Country ();
poland . setName ( "Poland" );
poland . setCapital ( "Warsaw" );
poland . setCurrency ( Currency . PLN );
poland . setPopulation ( 38186860 );
countries . add ( poland );
Country uk = new Country ();
uk . setName ( "United Kingdom" );
uk . setCapital ( "London" );
uk . setCurrency ( Currency . GBP );
uk . setPopulation ( 63705000 );
countries . add ( uk );
}
public Country findCountry ( String name ) {
Assert . notNull ( name );
Country result = null ;
for ( Country country : countries ) {
if ( name . equals ( country . getName ())) {
result = country ;
}
}
return result ;
}
}
建立國家服務終端
為了建立一個service endpoint,x需要一個pojo物件,以及一些Spring WS註解來處理SOAP請求:
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
package hello ;
import org.springframework.beans.factory.annotation.Autowired ;
import org.springframework.ws.server.endpoint.annotation.Endpoint ;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot ;
import org.springframework.ws.server.endpoint.annotation.RequestPayload ;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload ;
import io.spring.guides.gs_producing_web_service.GetCountryRequest ;
import io.spring.guides.gs_producing_web_service.GetCountryResponse ;
@Endpoint
public class CountryEndpoint {
private static final String NAMESPACE_URI = "http://spring.io/guides/gs-producing-web-service" ;
private CountryRepository countryRepository ;
@Autowired
public CountryEndpoint ( CountryRepository countryRepository ) {
this . countryRepository = countryRepository ;
}
@PayloadRoot ( namespace = NAMESPACE_URI , localPart = "getCountryRequest" )
@ResponsePayload
public GetCountryResponse getCountry ( @RequestPayload GetCountryRequest request ) {
GetCountryResponse response = new GetCountryResponse ();
response . setCountry ( countryRepository . findCountry ( request . getName ()));
return response ;
}
}
@Endpoint
向Spring WS註冊了該類為一個處理來臨的SOAP訊息的潛在物件。
@PayloadRoot
被Spring WS用來根據訊息的名稱空間 及localPart 來選擇處理該請求的方法。
@RequestPayload
指明來臨的訊息將被對映到該方法的request引數。
@ResponsePayload
註解將使得Spring WS將返回值與響應負載對映起來。
注意:在以上程式碼中,如果你沒有執行任務來根據WSDL生成領域物件,那麼在你的IDE中io.spring.guides類將會報告編譯時錯誤。
配置web service bean
使用Spring WS相關的bean配置選項建立一個新的類:
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
package hello ;
import org.springframework.boot.context.embedded.ServletRegistrationBean ;
import org.springframework.context.ApplicationContext ;
import org.springframework.context.annotation.Bean ;
import org.springframework.context.annotation.Configuration ;
import org.springframework.core.io.ClassPathResource ;
import org.springframework.ws.config.annotation.EnableWs ;
import org.springframework.ws.config.annotation.WsConfigurerAdapter ;
import org.springframework.ws.transport.http.MessageDispatcherServlet ;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition ;
import org.springframework.xml.xsd.SimpleXsdSchema ;
import org.springframework.xml.xsd.XsdSchema ;
@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
@Bean
public ServletRegistrationBean dispatcherServlet ( ApplicationContext applicationContext ) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet ();
servlet . setApplicationContext ( applicationContext );
servlet . setTransformWsdlLocations ( true );
return new ServletRegistrationBean ( servlet , "/ws/*" );
}
@Bean ( name = "countries" )
public DefaultWsdl11Definition defaultWsdl11Definition ( XsdSchema countriesSchema ) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition ();
wsdl11Definition . setPortTypeName ( "CountriesPort" );
wsdl11Definition . setLocationUri ( "/ws" );
wsdl11Definition . setTargetNamespace ( "http://spring.io/guides/gs-producing-web-service" );
wsdl11Definition . setSchema ( countriesSchema );
return wsdl11Definition ;
}
@Bean
public XsdSchema countriesSchema () {
return new SimpleXsdSchema ( new ClassPathResource ( "countries.xsd" ));
}
}
這裡Spring WS使用了不同的servlet型別來處理SOAP訊息:MessageDispatcherServlet
。注入及設定MessageDispatcherServlet
給ApplicationContext
是非常重要的。如果不這樣做,Spring WS無法自動檢測到Spring bean。
通過給dispatcherServlet
bean命名,替代 了Spring Boot中預設的DispatcherServlet bean
。
DefaultMethodEndpointAdapter
配置了註解驅動的Spring WS程式設計模型。這使得使用前面提過的諸如@Endpoint
等各種各樣的註解成為可能。
DefaultWsdl11Defination
使用XsdSchema
暴露了一個標準的WSDL 1.1。
請注意你需要為MessageDispatcherServlet
及DefaultWsdl11Definition
制定bean名稱,這是非常重要的。Bean名稱決定了生成的WSDL檔案在哪個web service是可用的。在本例中,WSDL可通過http://<host>:<port>/ws/countries.wsdl
來訪問。
該配置也使用了WSDL位置servlet轉化servlet.setTransformWsdlLocations(true)
。如果你訪問http://localhost:8080/ws/countries.wsdl ,soap:address
將擁有正確的值。如果你使用本機的公共IP來訪問該WSDL,你將看到的是IP。
建立該程式的可執行檔案
儘管我們可以將該程式打包成一個傳統的war包 並部署到一個外部的應用程式伺服器中,但是最簡單的方式還是下面所演示的,建立一個能獨立執行的應用程式。你可以通過老但好用的java main()
方法,將所有檔案打包到單個可執行的jar包中。同時,可以藉助於Spring的支援內建Tomcat servlet容器作為HTTP執行時,從而無需部署到外部的例項中。
src/main/java/hello/Application.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package hello ;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration ;
import org.springframework.boot.SpringApplication ;
import org.springframework.context.annotation.ComponentScan ;
@ComponentScan
@EnableAutoConfiguration
public class Application {
public static void main ( String [] args ) {
SpringApplication . run ( Application . class , args );
}
}
main()
方法使用了SpringApplication
輔助方法,將Application.class
作為引數傳遞給其自身的run()
方法。這告訴Spring讀取Application
中的註解後設資料,並將其作為Spring 應用程式上下文 的元件。
@ComponentScan
註解告訴Spring遞迴搜尋hello
包及其子包中找到被直接或者間接使用了Spring的@Component
註解的類。該指令確保了Spring發現並註冊CountryRepository
及CountriesEndpoint
,因為他們被標記為@Component
及@Endpoint
,這是一種@Component
註解。
@EnableAutoConfiguration
註解會基於classpath內容切換到預設的合理的行為。例如,由於應用程式依賴Tomcat的內建版本(tomcat-embed-core.jar),Spring會替你設定並配置一個預設的合理的Tomcat伺服器。並且該程式還依賴Spring MVC(spring-webmvc.jar),Spring會配置並註冊以惡搞Spring MVC DispatcherServlet
,根本無需web.xml
檔案!自動配置是強大的,彈性的機制。請檢視API文件 獲取更多細節。
構建可執行的jar包
你可以建立一個包含所有必須的依賴,類,及資源的可執行的JAR檔案。這很方便傳輸,版本管理以及獨立於部署生命週期來部署服務,跨不同的環境,諸如此類。
然後你可以執行WAR檔案:
1
java -jar build/libs/gs-soap-service-0.1.0.jar
如果你使用的是maven,你可以使用mvn spring-boot:run
來執行程式,或者你可以使用mvn clean package
構建JAR檔案,並使用下面命令來執行:
1
java -jar target/gs-soap-service-0.1.0.jar
注意:上面的產出物是以惡搞可執行JAR檔案。你也可以建立一個經典的WAR檔案 。
執行服務
如果使用的是Gradle,可以使用以下命令來執行服務:
1
./gradlew clean build && java -jar build/libs/gs-soap-service-0.1.0.jar
注意:如果你使用的是Maven,可以使用以下命令來執行服務:mvn clean package && java -jar target/gs-soap-service-0.1.0.jar
。
你也可以通過Gradle直接執行該程式:
注意:使用mvn的話,命令是mvn spring-boot:run
。
可以看到日誌輸出。該服務應該在幾秒鐘內啟動並執行起來。
測試該程式
現在該程式正在執行,你可以測試它。建立一個名為request.xml
檔案,包含以下的SOAP請求;
1
2
3
4
5
6
7
8
9
<soapenv:Envelope xmlns:soapenv= "http://schemas.xmlsoap.org/soap/envelope/"
xmlns:gs= "http://spring.io/guides/gs-producing-web-service" >
<soapenv:Header/>
<soapenv:Body>
<gs:getCountryRequest>
<gs:name> Spain</gs:name>
</gs:getCountryRequest>
</soapenv:Body>
</soapenv:Envelope>
有很多方式來測試該SOAP介面。你可以使用SoapUI 等工具,或者如果你使用的是*nix/Mac系統的話,直接可以使用命令列,如下所示:
1
$ curl --header "content-type: text/xml" -d @request.xml http://localhost:8080/ws
你將看到如下的響應結果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV= "http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns2:getCountryResponse xmlns:ns2= "http://spring.io/guides/gs-producing-web-service" >
<ns2:country>
<ns2:name> Spain</ns2:name>
<ns2:population> 46704314</ns2:population>
<ns2:capital> Madrid</ns2:capital>
<ns2:currency> EUR</ns2:currency>
</ns2:country>
</ns2:getCountryResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
注意:輸出可能是一個緊湊的XML文件,而不是上面顯示的格式友好的文件。如果系統中安裝了xmllib2,可以使用curl <args above> > output.xml | xmllint --format output.xml
來檢視格式友好的結果。
總結
恭喜你!你使用Spring Web Service開發完成了一個基於SOAP的服務。