目錄
- 開發環境
- 利用jaxb2的maven插件根據WSDL生成對應的POJO
- 開發和配置endpoint
- 配置web.xml
- 啓動servlet容器
- 驗證webservice服務的可用性
- 檢查wsdl
- 訪問webservice
- 有關spring-ws實現和其他使用的問題
- 參考資料
開發環境
eclipse ide | 4.3.2 |
spring | 4.0.6 |
spring-ws-core | 2.1.3 |
開發環境說明:之前沒了解過,以爲只有spring4.x纔有spring-ws的框架支持,後來看一下spring 3.x應該也是有對應版本的spring-ws,所以不一定版本需要用到這麼新,也可以根據自己的情況酌情選擇其他版本。
利用jaxb2的maven插件根據WSDL生成對應的POJO
創建項目目錄:
mkdir -p webservice_sample/src/{main,test}/{java,resources}; mkdir -p webservice_sample/src/main/webapp
目錄結構如下:
webservice_sample$ tree
.
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ ├── resources
│ │ └── webapp
│ └── test
│ ├── java
│ └── resources
└── target
├── classes
├── mvn-eclipse-cache.properties
└── test-classes
在src/main/resources/創建目錄 wsdl
在目錄 src/main/resources/wsdl 當中下載WSDL
wget http://webservice.webxml.com.cn/WebServices/IpAddressSearchWebService.asmx?WSDL
進入wsdl目錄後,將文件名中的問號改成句點,重命名爲 IpAddressSearchWebService.asmx.WSDL
進入目錄webservice_sample 編寫pom.xml,如下:
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>caesar.com</groupId> <artifactId>webservice_sample</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>mock Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <java_source_version>1.6</java_source_version> <java_target_version>1.6</java_target_version> <maven_compiler_plugin_version>2.5.1</maven_compiler_plugin_version> <maven_jaxb2_version>0.9.0</maven_jaxb2_version> <maven_jaxb2_forceRegenerate>false</maven_jaxb2_forceRegenerate> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.ws</groupId> <artifactId>spring-ws-core</artifactId> <version>2.1.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.ws</groupId> <artifactId>spring-xml</artifactId> <version>2.1.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.ws</groupId> <artifactId>spring-ws-support</artifactId> <version>2.1.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.ws</groupId> <artifactId>spring-oxm</artifactId> <version>1.5.10</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.jdom</groupId> <artifactId>jdom2</artifactId> <version>2.0.5</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.6</version> </dependency> <dependency> <groupId>org.eclipse.jetty.aggregate</groupId> <artifactId>jetty-all</artifactId> <version>7.2.0.v20101020</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>apache-log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.15</version> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/java/</directory> </resource> <resource> <directory>src/main/resources/</directory> </resource> <resource> <directory>src/main/webapp/</directory> </resource> </resources> <finalName>mock</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy</id> <phase>install</phase> <goals> <goal>copy-dependencies</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>7.2.0.v20101020</version> <configuration> <!-- specify jetty port --> <jettyConfig>${basedir}/src/main/resources/jetty.xml</jettyConfig> </configuration> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>${maven_compiler_plugin_version}</version> <configuration> <source>${java_source_version}</source> <target>${java_target_version}</target> <encoding>UTF-8</encoding> </configuration> </plugin> <!-- disable genereate java code from wsdl begin --> <!-- wsdl to java code for separate av provider --> <plugin> <groupId>org.jvnet.jaxb2.maven2</groupId> <artifactId>maven-jaxb2-plugin</artifactId> <version>${maven_jaxb2_version}</version> <executions> <execution> <id>alibaba_av</id> <goals> <goal>generate</goal> </goals> <configuration> <schemaLanguage>WSDL</schemaLanguage> <generateDirectory>${basedir}/src/main/java/</generateDirectory> <generatePackage>cn.com.webxml.webservice.wsdl.ipaddresssearch</generatePackage> <forceRegenerate>${maven_jaxb2_forceRegenerate}</forceRegenerate> <encoding>UTF-8</encoding> <schemas> <schema> <!-- <url>http://webservice.webxml.com.cn/WebServices/WeatherWS.asmx?WSDL</url> --> <url>file:${basedir}/src/main/resources/wsdl/IpAddressSearchWebService.asmx.WSDL</url> </schema> </schemas> </configuration> </execution> </executions> </plugin> <!-- disable genereate java code from wsdl end --> </plugins> </build> </project>
在 webservice_sample 目錄中執行如下指令,創建eclipse工程
mvn eclipse:clean eclipse:eclipse -DdownloadSources=true
導入eclipse中,會看到如下截圖:
包路徑 cn.com.webxml.webservice.wsdl.ipaddresssearch 中的代碼就是我們在pom當中如下這段聲明生成的(具體配置方法可以參考插件所在的網站文檔):
<plugin> <groupId>org.jvnet.jaxb2.maven2</groupId> <artifactId>maven-jaxb2-plugin</artifactId> …… </plugin>
可能有細心的朋友會發現生成WSDL文檔對應的pojo代碼的聲明內容當中註釋了一段
<!-- <url>http://webservice.webxml.com.cn/WebServices/WeatherWS.asmx?WSDL</url> -->本來是打算用這個webservice來做示例,但是因爲執行mvn eclipse:eclipse的時候發生了錯誤,大家可以自己試驗一下看看問題在哪裏(我暫時沒有去排查這個原因,所以先存疑。)
開發和配置endpoint
前面只是準備好了wsdl和與xml的對應轉換pojo而已,現在要看看如何開發endpoint
打開IpAddressSearchWebService.asmx.WSDL,如下圖所示:
準備開發一個soap的方法 getCountryCityByIp,我們可以編寫如下endpoint:
package cn.com.webxml.webservice.endpoint;
import java.util.Arrays;
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 org.springframework.ws.soap.SoapHeader;
import org.springframework.ws.soap.SoapMessage;
import cn.com.webxml.webservice.wsdl.ipaddresssearch.ArrayOfString;
import cn.com.webxml.webservice.wsdl.ipaddresssearch.GetCountryCityByIp;
import cn.com.webxml.webservice.wsdl.ipaddresssearch.GetCountryCityByIpResponse;
@Endpoint
public class IpAddrSearchEndpoint {
private static final String NAMESPACE_URI = "http://WebXml.com.cn/";
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCountryCityByIp")
@ResponsePayload
public GetCountryCityByIpResponse getCountryCityByIp(@RequestPayload GetCountryCityByIp request,
SoapHeader soapHeader, SoapMessage soapMessage) {
// output(soapMessage);
GetCountryCityByIpResponse response = new GetCountryCityByIpResponse();
ArrayOfString value = new ArrayOfString() {
{
this.string = Arrays.asList("hongdulasi", "nibo'er");
}
};
response.setGetCountryCityByIpResult(value);
return response;
}
}
這裏求簡,在訪問這個方法的時候,默認只反饋一個固定內容的字符串數組。
這裏需要注意幾點:
- 這個endpoint類的包路徑(後面會用到)是 cn.com.webxml.webservice.endpoint
- 類頭部上的@Endpoint標註
- 方法聲明上的 @PayloadRoot 標註中的namespace和localPart分別就是wsdl中的targetNamespace和soap方法名稱
- @ResponsePayload 和 @RequestPayload 這兩個標註的用法,以及它們對應的數據類型就是此前通過maven插件對wsdl定義生成的java類
配置web.xml
雖然有了endpoint,我們依舊無法奔跑(run起來)我們的webservice的服務端,嗯,需要有一個servlet容器以及web.xml配置來銜接我們的spring容器以及endpoint類到運行時狀態。
準備一個 web-ipaddresssearch.xml
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>ipaddrsearch-spring-ws</servlet-name> <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>ipaddrsearch-spring-ws</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
還有一個spring的配置文件 ipaddrsearch-spring-ws-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:sws="http://www.springframework.org/schema/web-services" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:component-scan base-package="cn.com.webxml.webservice.endpoint" /> <!-- 這裏是讓spring容器掃描這個包路徑下的標註,這裏就用到上面的endpoint所在的包路徑了,當然可以指定更高一級的路徑,擴大掃描的範圍 --> <sws:annotation-driven /> <sws:static-wsdl id="IpAddressSearchWebService" location="classpath:wsdl/IpAddressSearchWebService.asmx.WSDL"/> <!-- 這裏是用來指定靜態wsdl定義的配置 --> </beans>
需要注意的內容:
web-ipaddresssearch.xml 和 ipaddrsearch-spring-ws-servlet.xml 之間是有對應關係的。
web-ipaddresssearch.xml 中的 “servlet-name”的內容就是 ipaddrsearch-spring-ws-servlet.xml 的前半部分。
配置endpoint的文件名稱的命名規範可以看成是: <servlet-name>-servlet.xml 【其中<servlet-name>需要用你在web.xml當中配置的servlet-name的名稱去替換】
啓動servlet容器
好了都準備好了,該上servlet容器了,我才用了手寫代碼的笨辦法,主要是少點配置,多點代碼好調整一些。
package mock.webservice.server.main;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.webapp.WebAppContext;
public class RunJetty {
private static final String JETTY_CONNECTOR_NAME = "webservice_connector";
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Server server = new Server();
String currentPath = RunJetty.class.getResource("/").getPath();
System.out.println("currentPath = " + currentPath);
HandlerList handlerList = new HandlerList();
SelectChannelConnector connector_8080 = new SelectChannelConnector();
connector_8080.setPort(8080); // 端口號
connector_8080.setMaxIdleTime(30000);
connector_8080.setRequestHeaderSize(8192);
connector_8080.setName(JETTY_CONNECTOR_NAME);
server.addConnector(connector_8080);
WebAppContext customerWebAppContext = new WebAppContext();
customerWebAppContext.setDescriptor(String.format("%s/WEB-INF/web-ipaddresssearch.xml", currentPath));
customerWebAppContext.setResourceBase(currentPath);
customerWebAppContext.setContextPath("/ipaddress"); // context path
customerWebAppContext.setConnectorNames(new String[] { JETTY_CONNECTOR_NAME });
handlerList.addHandler(customerWebAppContext);
server.setHandler(handlerList);
server.start();
server.join();
}
}
直接啓動RunJetty類,就能訪問我們暴露的webservice的服務了。
驗證webservice服務的可用性
檢查wsdl
可以先通過
curl http://localhost:8080/ipaddress/IpAddressSearchWebService.wsdl
來查看wsdl定義。(注意:spring-ws框架的wsdl的訪問路徑的固定後綴就是wsdl,而其名稱就是前面<sws:static-wsdl/> 中定義的id值)
訪問webservice
首先訪問官方的測試文檔,打開URL: http://webservice.webxml.com.cn/WebServices/IpAddressSearchWebService.asmx?op=getCountryCityByIp
拷貝soap1.1中的內容,並稍作調整:
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <getCountryCityByIp xmlns="http://WebXml.com.cn/"> <theIpAddress>test test</theIpAddress> </getCountryCityByIp> </soap:Body> </soap:Envelope>
將這段內容保存在 /tmp/ipaddrsearch.xml 中,而後在命令行下使用curl訪問webservice
curl -H "Content-Type:text/xml;charset=utf-8" -d @/tmp/ipaddrsearch.xml http://localhost:8080/ipaddress/IpAddressSearchWebService.asmx > /tmp/xml.tmp; xmllint --format /tmp/xml.tmp
可以得到反饋信息:
<?xml version="1.0"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header/> <SOAP-ENV:Body> <ns2:getCountryCityByIpResponse xmlns:ns2="http://WebXml.com.cn/"> <ns2:getCountryCityByIpResult> <ns2:string>hongdulasi</ns2:string> <ns2:string>nibo'er</ns2:string> </ns2:getCountryCityByIpResult> </ns2:getCountryCityByIpResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
我們期望的結果出現了。
需要注意的是:
其實我們的訪問URL並沒有特別的約束,其核心部分是:
curl -H "Content-Type:text/xml;charset=utf-8" -d @/tmp/ipaddrsearch.xml http://localhost:8080/ipaddress/IpAddressSearchWebService.asmx
這裏的URL後面的“IpAddressSearchWebService.asmx”這段可以改成其他任何字符串都是ok的。
因爲在 /tmp/ipaddrsearch.xml 當中的請求內容已經將webservice請求的namespace和soap方法說明的比較清楚了,spring-ws框架已經能夠定位到我們所編寫的endpoint類。
有關spring-ws實現和其他使用的問題
【實現】spring-ws是如何定位到endpoint類其中的方法的?
【使用】文中沒有提到一個很常用的場景——soapheader進行權限驗證應該如何實現?
參考資料
spring官方文檔: http://docs.spring.io/spring-ws/docs/2.2.0.RELEASE/reference/htmlsingle/
如果需要觀察webservice調用情況,可以通過tcpdump獲取抓包的內容(比如文中端口是8080,網卡假定名稱爲eth1,操作系統爲linux)寫入一個固定爲(比如下面指令的 /tmp/capture),則可以使用如下指令:
sudo tcpdump -i eth1 port 8080 -w /tmp/capture
這篇文檔講了如何將jdk的DomSource、String類型的xml文檔輸出成稍微有點縮進的樣子(雖然不夠pretty,但是也還好了)
http://stackoverflow.com/questions/139076/how-to-pretty-print-xml-from-java
自動拷貝依賴包(如果需要將文中的代碼打包放到某臺固定機器的話,會需要所有依賴包合併的到一起,方便啓動)
http://www.ibm.com/developerworks/cn/java/j-5things13/