Spring ws 小示例

一、简介
  Spring Web Service 致力于开发契约优先的SOAP web Service,可以灵活利用很多方式处理XML内容;契约优先指的是我们在开始编写一个Web Service时,先编写WSDL,或者是XSD文件后动态生成WSDL,而不是先编写java code,然后根据代码生成WSDL。主要的特点有:
  1.强大的Mapping功能:你可以基于message内容、SOAP Action header 或者Xpath 表达式将接收到的XML请求映射到任意的Object。
  2.支持大量的XML API:不仅仅可以用JAXP APIS 如DOM,SAX,Stax,也可以用 JDOM,dom4j,XOM,甚至直接用编组技术(marshalling technologies,能直接将xml内容转变成java object)操作xml。
  3.灵活的XML编组:Spring Web Services 建立在spring framework 的oxm模块,支持JAXB1和JAXB2、Castor、XMLBeans、JiBX以及XStream。
  4.支持消息安全机制:Spring WS的安全机制允许你签名SOAP消息,加密和解密,也可以追加认证。

二、示例说明
  本示例参照Spring-ws-refenrenc中讲解的例子,实现一个hoiday预定功能,并在此基础上做了一些扩展,采用JAXB2直接将请求Object转变成消息发送,预定成功后返回姓名以及预定号码,并直接将返回结果转变成Object。

三、示例开始,首先编辑XSD
  Spring-WS 不推荐直接编写wsdl文件,我们可以编写相对简单的XSD文件,然后生成对应的wsdl文件。
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
	xmlns:hr="http://fengyilin.com/hr/schemas" elementFormDefault="qualified"
	targetNamespace="http://fengyilin.com/hr/schemas">
	<xs:element name="HolidayRequest">
		<xs:complexType>
			<xs:all>                           (4)
				<xs:element name="Holiday" type="hr:HolidayType" />     (1)
				<xs:element name="Employee" type="hr:EmployeeType" />         			</xs:all>
		</xs:complexType>
	</xs:element>
	<xs:element name="HolidayResponse">
		<xs:complexType>
			<xs:sequence>           (5)
				<xs:element name="name" type="xs:string" />
				<xs:element name="number" type="xs:integer" /> (3)
			</xs:sequence>
		</xs:complexType>
	</xs:element>
	<xs:complexType name="HolidayType">
		<xs:sequence>
			<xs:element name="StartDate" type="xs:date" />    (2)
			<xs:element name="EndDate" type="xs:date" />      (2)
		</xs:sequence>
	</xs:complexType>
	<xs:complexType name="EmployeeType">
		<xs:sequence>
			<xs:element name="FirstName" type="xs:string" />   (3)
			<xs:element name="LastName" type="xs:string" />
		</xs:sequence>
	</xs:complexType>
</xs:schema>

(1) 引用我们自己定义的命名空间下的数据类型
(2)采用xsd:date data type,包含年、月、日
(3)xsd:string、xsd:integer定义对应的属性
(4)xsd:all 表明属性<Holiday/>和<Employee/>的顺序是随意
(5)xsd:sequence 表明属性顺序要保持一致
四、生成wsdl
1.创建动态web工程SpringWebService
2.编辑web.xml
 
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
	id="WebApp_ID" version="3.1">
	<display-name>SpringWebService</display-name>

	<servlet>
		<servlet-name>spring-ws</servlet-name>
		<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<init-param>
			<param-name>transformWsdlLocations</param-name>
			<param-value>true</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
		<async-supported>true</async-supported>
	</servlet>
	<servlet-mapping>
		<servlet-name>spring-ws</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	<context-param>
		<param-name>log4jConfigLocation</param-name>
		<param-value>/resources/log4j/log4j.properties</param-value>
	</context-param>
	<listener>
		<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
	</listener>
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
	</welcome-file-list>
</web-app>



与SpringMvc工程相比,这里只是把DispatcherServlet换成了MessageDispatcherServlet。把transformWsdlLoactions属性设置成true,启动location 转换功能,从而将相对路径动态的转换成绝对路径。
3.编辑context配置文件
<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="test.spring.ws.service" /> (1)
	<sws:annotation-driven />                                    (2)
	<sws:dynamic-wsdl id="holiday"                          (3)
		portTypeName="HumanResource"             (4)
		locationUri="/holidayService/"                    (5)
		targetNamespace="http://fengyilin.com/hr/definitions">   (6)
		<sws:xsd location="/resources/xsd/hr.xsd" />        (7)
	</sws:dynamic-wsdl>
</beans>

(1)指定类扫描路径
(2)标明采用注解驱动
(3)id属性表明了WSDL可以被获取到的URL,这里id是holiday,标明WSDL文件在servlet context中是 holiday.wsdl.完整的获取路径是:http://host:port/工程名/(locationUri)/holiday.wsdl,对应到本示例就是http://localhost:8080/SpringWebService/holidayService/holiday.wsdl;dynamic-wsdl标明在程序执行中动态生成wsdl
(4)指定WSDL的port type是HumanResource
(5)指定了一个相对路径,因为在web.xml中设置了transformWsdlLocations的值为true,所以此处能用相对路径标明wsdl service的访问路径。
(6)指定了WDSL自己的命名空间,如果不设置,wsdl使用和XSD schema相同的命名空间
(7)指定了用来生成wsdl的xsd文件的位置
4.应用启动后,访问对应的url,生成wsdl文件,内容如下
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
	xmlns:sch="http://fengyilin.com/hr/schemas" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
	xmlns:tns="http://fengyilin.com/hr/definitions" targetNamespace="http://fengyilin.com/hr/definitions">
	<wsdl:types>
		<xs:schema xmlns:hr="http://fengyilin.com/hr/schemas"
			xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
			targetNamespace="http://fengyilin.com/hr/schemas">
			<xs:element name="HolidayRequest">
				<xs:complexType>
					<xs:all>
						<xs:element name="Holiday" type="hr:HolidayType" />
						<xs:element name="Employee" type="hr:EmployeeType" />
					</xs:all>
				</xs:complexType>
			</xs:element>
			<xs:element name="HolidayResponse">
				<xs:complexType>
					<xs:sequence>
						<xs:element name="name" type="xs:string" />
						<xs:element name="number" type="xs:integer" />
					</xs:sequence>
				</xs:complexType>
			</xs:element>
			<xs:complexType name="HolidayType">
				<xs:sequence>
					<xs:element name="StartDate" type="xs:date" />
					<xs:element name="EndDate" type="xs:date" />
				</xs:sequence>
			</xs:complexType>
			<xs:complexType name="EmployeeType">
				<xs:sequence>
					<xs:element name="FirstName" type="xs:string" />
					<xs:element name="LastName" type="xs:string" />
				</xs:sequence>
			</xs:complexType>
		</xs:schema>
	</wsdl:types>
	<wsdl:message name="HolidayRequest">
		<wsdl:part element="sch:HolidayRequest" name="HolidayRequest"></wsdl:part>
	</wsdl:message>
	<wsdl:message name="HolidayResponse">
		<wsdl:part element="sch:HolidayResponse" name="HolidayResponse"></wsdl:part>
	</wsdl:message>
	<wsdl:portType name="HumanResource">
		<wsdl:operation name="Holiday">
			<wsdl:input message="tns:HolidayRequest" name="HolidayRequest"></wsdl:input>
			<wsdl:output message="tns:HolidayResponse" name="HolidayResponse"></wsdl:output>
		</wsdl:operation>
	</wsdl:portType>
	<wsdl:binding name="HumanResourceSoap11" type="tns:HumanResource">
		<soap:binding style="document"
			transport="http://schemas.xmlsoap.org/soap/http" />
		<wsdl:operation name="Holiday">
			<soap:operation soapAction="" />
			<wsdl:input name="HolidayRequest">
				<soap:body use="literal" />
			</wsdl:input>
			<wsdl:output name="HolidayResponse">
				<soap:body use="literal" />
			</wsdl:output>
		</wsdl:operation>
	</wsdl:binding>
	<wsdl:service name="HumanResourceService">
		<wsdl:port binding="tns:HumanResourceSoap11" name="HumanResourceSoap11">
			<soap:address location="http://localhost:8080/SpringWebService/holidayService" />
		</wsdl:port>
	</wsdl:service>
</wsdl:definitions>


*尽管在运行时从XSDs直接生成WSDL十分便利,采用这种方式会有以下几个问题:
(1)随着Spring-ws版本升级,根据相同的XSD生成的WSDL有可能会发生变化。
(2)即便是只生成一次,然后将wsdl缓存起来,但是因为生成过程也会造成请求变慢。
所以推荐做法是:在development阶段,用<dynamic-wsdl>来动态生成,当发布应用时,利用浏览器将wsdl download下来,然后再用<static-wsdl>,这样就可以保证WSDL保持不变

即把上文的servlet-context.xml中关于sws的部分改为如下内容
<sws:static-wsdl id="holiday" location="/resources/wsdl/holiday.wsdl"/>


五、实现Endpoint
因为采用了JAXB2的组装技术,所以没有了xml的操作,Endpoint实现相当简单
package test.spring.ws.service.endpoint;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Source;

import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.filter.Filters;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;
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 org.springframework.xml.xpath.Jaxp13XPathTemplate;
import org.springframework.xml.xpath.XPathOperations;

import test.spring.ws.entity.HolidayRequest;
import test.spring.ws.entity.HolidayResponse;
import test.spring.ws.service.HumanResourceService;

@Endpoint
public class HolidayEndpoint {
	private static final String NAMESPACE_URI = "http://fengyilin.com/hr/schemas";

	private HumanResourceService humanResourceService;

	@Autowired
	public HolidayEndpoint(HumanResourceService humanResourceService) throws JDOMException {
		this.humanResourceService = humanResourceService;
	}

	@PayloadRoot(namespace = NAMESPACE_URI, localPart = "HolidayRequest") (1)
	@ResponsePayload     (2)
	public HolidayResponse handleHolidayRequest(@RequestPayload HolidayRequest request) throws Exception {   (3)

		humanResourceService.bookHoliday(request.getHoliday().getStartDate(), request.getHoliday().getEndDate(),
				request.getEmployee().getFirstName());

		HolidayResponse response = new HolidayResponse();
		response.setName(request.getEmployee().getFirstName()+"_"+request.getEmployee().getLastName());
		response.setNumber((int) (100*Math.random()));
		return response;
	}
}


(1)因为SOAP协议和transport无关的,所以Spring-ws不支持通过HTTP请求的URL来Mapping消息到Endpoint,而是通过消息的内容,包括消息的命名空间,消息本地名称,所以PayloadRoot注解中的属性一定要正确,并且和客户端调用时保持一致。
(2)ResponsePayload注解表示有内容返回给客户端
(3)HolidayResponse和HolidayRequest是利用JAXB注解注释的类,可以利用xsd文件直接生成。(此处为了理解xsd到java class直接的转换,我手工写了这两个类,以及它们依赖的类),如何自动生成,可以参照 http://fengyilin.iteye.com/admin/blogs/2344183

以上就是服务端的主要代码,下面简单看下客户端调用
/*
 * Copyright 2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package test.spring.ws.client;

import java.io.IOException;
import java.util.Date;

import javax.xml.transform.Source;

import org.springframework.core.io.ClassPathResource;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.util.ClassUtils;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.xml.transform.ResourceSource;
import org.springframework.xml.transform.StringResult;

import test.spring.ws.entity.EmployeeType;
import test.spring.ws.entity.HolidayRequest;
import test.spring.ws.entity.HolidayResponse;
import test.spring.ws.entity.HolidayType;
import test.spring.ws.jaxb2.JaxbUtil;

public class HolidayClient {

	private final WebServiceTemplate webServiceTemplate = new WebServiceTemplate();

	// send to the configured default URI
	public void sendAndReceive() throws Exception {
		Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
		marshaller.setPackagesToScan(new String[] { ClassUtils.getPackageName(HolidayRequest.class) });
		marshaller.afterPropertiesSet();
		WebServiceTemplate msbServiceTemplate = new WebServiceTemplate(marshaller);  -- (1)
		HolidayRequest request = new HolidayRequest();
		EmployeeType employee = new EmployeeType();
		employee.setFirstName("cc");
		employee.setLastName("dd");
		request.setEmployee(employee);

		HolidayType holiday = new HolidayType();
		holiday.setEndDate(new Date());
		holiday.setStartDate(new Date());

		request.setHoliday(holiday);
		msbServiceTemplate.setDefaultUri("http://localhost:8080/SpringWebService/services");
		HolidayResponse response = (HolidayResponse) msbServiceTemplate.marshalSendAndReceive(request);--(2)
		
		
		System.out.println("-------");
		System.out.println(response);
		System.out.println("-------");
	}

	public static void main(String[] args) throws Exception {
		HolidayClient client = new HolidayClient();
		client.sendAndReceive();
	}
}


(1)采用编组技术,直接将Java Object转换成消息内容发送
(2)返回结果直接组装成java 对象

完整的工程目录如下:



工程的具体代码请参考附件

猜你喜欢

转载自fengyilin.iteye.com/blog/2344153