spring-ws框架開發webservice服務

目錄

  • 開發環境
  • 利用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;
    }

}
 

 

這裏求簡,在訪問這個方法的時候,默認只反饋一個固定內容的字符串數組。

 

這裏需要注意幾點:

  1. 這個endpoint類的包路徑(後面會用到)是 cn.com.webxml.webservice.endpoint
  2. 類頭部上的@Endpoint標註
  3. 方法聲明上的 @PayloadRoot 標註中的namespace和localPart分別就是wsdl中的targetNamespace和soap方法名稱
  4. @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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章