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 对象
完整的工程目录如下:
工程的具体代码请参考附件