目录
- 开发环境
- 利用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/