因为工作需要,在基本没这么玩过web service的情况下,对接公司的ESB(主要基于web service)系统,由于项目基本是基于spring的,因此直接入手spring web service,虽然项目中功能简单,用到的内容也很少,在看文档的过程中,一时兴起,看完整个文档,并根据自己的理解做了简单翻译。然而由于个人技术水平和英文水平都不高,并且工作也比较繁忙,陆陆续续一个月时间才完成,质量也不是很高,有错误的地方欢迎指出,当然最好的方式还是直接查看官方文档,此文只望能让看到的人对spring web service稍微有个了解。
spring webservice官网
源码库
本文参考spring-ws 3.0.7.RELEASE版本,所有代码示例以及图片均来自官网。
概述
开发webservice一般有两种方式,契约先行和契约后行。这里的契约指的是wsdl。
契约先行
即先编写WSDL,XSD文件约定好接口,然后再编写代码。
契约先行:
- WSDL是服务的定义,是一个需要多方遵守的规则,因此应当尽可能的保持WSDL的固化;
- 将代码转成XML时,没有标准的做法,只能自我实现,各语言的数据接口跟XML差异比较大;
- 一般开发语言难以进行规则校验,Java则更不方便;
- 各种开发语言的类库不同,无法有统一的实现,XML可以作为中间转换标准,可以支持所有语言;
- 代码中迭代结构转换XML,形成死循环,无法结束;
契约后行:
- 代码改动后,需要重新发布WSDL,可能引起服务调用方的调整;
- 代码转换成XML,可能会讲所有涉及到的类全部转换,严重耗费资源
spring webservice倡导契约先行的方式开发webservice.
spring-ws整体结构图

XSD
spring-ws的数据协议采用XML Schema(XSD)。
基础版:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="http://mycompany.com/hr/schemas"
xmlns:hr="http://mycompany.com/hr/schemas">
<xs:element name="HolidayRequest">
<xs:complexType>
<xs:sequence>
<xs:element ref="hr:Holiday"/>
<xs:element ref="hr:Employee"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Holiday">
<xs:complexType>
<xs:sequence>
<xs:element ref="hr:StartDate"/>
<xs:element ref="hr:EndDate"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="StartDate" type="xs:NMTOKEN"/>
<xs:element name="EndDate" type="xs:NMTOKEN"/>
<xs:element name="Employee">
<xs:complexType>
<xs:sequence>
<xs:element ref="hr:Number"/>
<xs:element ref="hr:FirstName"/>
<xs:element ref="hr:LastName"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Number" type="xs:integer"/>
<xs:element name="FirstName" type="xs:NCName"/>
<xs:element name="LastName" type="xs:NCName"/>
</xs:schema>
以上XML可以得到改进,首先注意到每种类型都包含有根节点元素的声明,说明webservice需要接受每一种元素数据,单这并不是我们想要的,其实上面的XML我们只想接受HolidayRequest而已,因此我们可以移除根节点级别的element,做如下改进:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:hr="http://mycompany.com/hr/schemas"
elementFormDefault="qualified"
targetNamespace="http://mycompany.com/hr/schemas">
<xs:element name="HolidayRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="Holiday" type="hr:HolidayType"/>
<xs:element name="Employee" type="hr:EmployeeType"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="HolidayType">
<xs:sequence>
<xs:element name="StartDate" type="xs:NMTOKEN"/>
<xs:element name="EndDate" type="xs:NMTOKEN"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="EmployeeType">
<xs:sequence>
<xs:element name="Number" type="xs:integer"/>
<xs:element name="FirstName" type="xs:NCName"/>
<xs:element name="LastName" type="xs:NCName"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
我们仍然可以针对以schema做一个校验限制
<HolidayRequest xmlns="http://mycompany.com/hr/schemas">
<Holiday>
<StartDate>this is not a date</StartDate>
<EndDate>neither is this</EndDate>
</Holiday>
PlainText Section qName:lineannotation level:4, chunks:[<, !-- ... --, >] attrs:[:]
</HolidayRequest>
XML可以绑定数据类型,因此我们的最终版XSD如下:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:hr="http://mycompany.com/hr/schemas"
elementFormDefault="qualified"
targetNamespace="http://mycompany.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: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="Number" type="xs:integer"/>
<xs:element name="FirstName" type="xs:string"/>
<xs:element name="LastName" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
服务契约
服务契约通常是通过WSDL文件描述的。spring-ws提供根据XSD和规则配置生成WSDL文件,因此手工编写WSDL在并不是必须的。
编写WSDL步骤
-
以一个标准的方式开始,并引入存在的XSD文件(此处XSD也可以是已经发布到公共地址上的公开XSD)
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:schema="http://mycompany.com/hr/schemas" xmlns:tns="http://mycompany.com/hr/definitions" targetNamespace="http://mycompany.com/hr/definitions"> <wsdl:types> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:import namespace="http://mycompany.com/hr/schemas" schemaLocation="hr.xsd"/> </xsd:schema> </wsdl:types>
-
定义message,message可以有多个
<wsdl:message name="HolidayRequest"> <wsdl:part element="schema:HolidayRequest" name="HolidayRequest"/> </wsdl:message>
-
将message作为一个动作加入到port type中
<wsdl:portType name="HumanResource"> <wsdl:operation name="Holiday"> <wsdl:input message="tns:HolidayRequest" name="HolidayRequest"/> </wsdl:operation> </wsdl:portType>
-
binding
定义客户端如何引用动作 -
service
定义服务所在位置
前3步定义WSDL的抽象部分,4/5步为具体定义
最终完整的WSDL文件:
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:schema="http://mycompany.com/hr/schemas"
xmlns:tns="http://mycompany.com/hr/definitions"
targetNamespace="http://mycompany.com/hr/definitions">
<wsdl:types>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:import namespace="http://mycompany.com/hr/schemas"
schemaLocation="hr.xsd"/>
</xsd:schema>
</wsdl:types>
<wsdl:message name="HolidayRequest">
<wsdl:part element="schema:HolidayRequest" name="HolidayRequest"/>
</wsdl:message>
<wsdl:portType name="HumanResource">
<wsdl:operation name="Holiday">
<wsdl:input message="tns:HolidayRequest" name="HolidayRequest"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="HumanResourceBinding" type="tns:HumanResource">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="Holiday">
<soap:operation soapAction="http://mycompany.com/RequestHoliday"/>
<wsdl:input name="HolidayRequest">
<soap:body use="literal"/>
</wsdl:input>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="HumanResourceService">
<wsdl:port binding="tns:HumanResourceBinding" name="HumanResourcePort">
<soap:address location="http://localhost:8080/holidayService/"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Endpoint
Endpoint是处理请求的XML消息的实现,通常通过@Endpoint
注解来完成,在endpoint类中可以定义多个方法来处理不同的请求。
路由@PayloadRoot
@PayloadRoot
注解用语指明当前方法可以处理哪一类的请求。spring-ws中路由过程由EndpointMapping
负责。
通过使用PayloadRootAnnotationMethodEndpointMapping
实现根据消息内容来路由
@PayloadRoot(namespace = "http://mycompany.com/hr/schemas", localPart = "HolidayRequest")
public void handleHolidayRequest(@RequestPayload Element holidayRequest) throws Exception {
///////
}
发布WSDL
传统XML配置方式
<sws:dynamic-wsdl id="holiday"
portTypeName="HumanResource"
locationUri="/holidayService/"
targetNamespace="http://mycompany.com/hr/definitions">
<sws:xsd location="/WEB-INF/hr.xsd"/>
</sws:dynamic-wsdl>
web.xml中MessageDispacherServlet增加初始化配置
<init-param>
<param-name>transformWsdlLocations</param-name>
<param-value>true</param-value>
</init-param>
@Configration
方式
在创建MessageDispatcherServlet bean时,设置transformWsdlLocations为true即可
@Bean
public ServletRegistrationBean messageDispacherServlet(ApplicationContext applicationContext){
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/services/*");
}
两种方式都可以动态发布WSDL,即不需要手动编写WSDL,在浏览器中类似http://localhost:8080/holidayService/holiday.wsdl
链接即可看到WSDL。
共享组件
在spring-ws中,在客户端和服务端会有很多共享的组件内容,它们共同展现了构建的webservice,这些组件可能我们并不会直接引用,但是理解它们十分有必要。
webservice message
WebServiceMessage
spring-ws中的一个核心接口。WebServiceMessage
展示了与协议无关的XML消息,接口代码很简单,只有定义了三个方法,返回消息载体的javax.xml.transform.Source
和javax.xml.transform.Result
以及一个写出消息流的方法。
public interface WebServiceMessage {
Source getPayloadSource();
Result getPayloadResult();
void writeTo(OutputStream var1) throws IOException;
}
XML的操作方式有很多种,不通操作对Source
和Result
的包装如下所示:
Source/Result的实现 | 包装的XML |
---|---|
javax.xml.transform.dom.DOMSource |
org.w3c.dom.Node |
javax.xml.transform.dom.DOMResult |
org.w3c.dom.Node |
javax.xml.transform.sax.SAXSource |
org.xml.sax.InputSource and org.xml.sax.XMLReader
|
javax.xml.transform.sax.SAXResult |
org.xml.sax.ContentHandler |
javax.xml.transform.stream.StreamSource |
java.io.File , java.io.InputStream , or java.io.Reader
|
javax.xml.transform.stream.StreamResult |
java.io.File , java.io.OutputStream , or java.io.Writer
|
SoapMessage
SoapMessage
是WebServiceMessage
的实现类,包含了SOAP的一些特性方法。一般情况下,不应当依赖或者直接使用SoapMessage
,因为我们可以在WebServiceMessage
中通过getPayloadSource()
和getPayloadResult()
获取到SOAP消息体的内容,因此只有在需要增加Header、获取附件等SOAP特定的动作时,才应当将WebServiceMessage
转换为SoapMessage
。
消息工厂
spring-ws的消息主要通过WebServiceMessageFactory
来生产,该接口之定义了两个方法,一个送来生产空消息,一个通过消息流生产消息
public interface WebServiceMessageFactory {
WebServiceMessage createWebServiceMessage();
WebServiceMessage createWebServiceMessage(InputStream inputStream) throws InvalidXmlException, IOException;
}
WebServiceMessageFactor
主要有两个具体实现,SAAJ(SOAP with Attachments API for Java)和基于AXIS2的AXIOM(AXIS Object Model)。
-
SaajSoapMessageFactory
从J2EE1.4开始就存在,可以适用于大部分的应用服务器。
SAAJ是基于DOM的,所有的SOAP消息都是保存在内存中的,因此如果消息体比较大或者内存紧张的情况,不适合使用SAAJ,而应当适用AXIOM。
weblogic9实现SAAJ1.2有一个BUG,虽然它实现了SAAJ1.2的所有接口,但是在调用时会抛出
UnsupportedOperationException
,spring-ws在weblogic9中通过使用SAAJ1.1版本来解决这个问题。 -
AxiomSoapMessageFactory
AxiomSoapMessageFactoyr
使用AXIS2 Object Model来创建Soap消息,它主要基于StAX(Stream API for XML)。StAX读取XML消息的时候采取拉取的机制,因此读取大消息的时候效率会更好。
需要注意
payloadCaching
的设置,默认为true,表示消息只能读取一次。AXIOM支持在
StreamingWebServiceMessage
中定义的完整消息流,可以处理支持JAXB2的对象。
SaajSoapMessageFactor
和AxiomSoapMessageFactory
都有soapVersion
属性,默认是 1.1,也可以设置为1.2
MessageContext
在webservice中,消息通常是成对出现的。客户端创建请求消息,经过一系列的转换发送到服务端;服务端创建响应消息返回给客户端。
spring-ws在一个请求链中存在一个上下文,即MessageContext
,保存了请求和响应消息的属性。客户端的上下文是WebServiceTemplate
创建的,服务端的上下文是从指定的输入流中读取的,比如HTTP是从HttpServletRequest
中读取,HttpServletResponse
中响应。
TransportContext
SOAP的一个关键信息是传输无关,因此spring-ws不支持HTTP通过URL请求,而是支持通过消息内容来请求。
有时候可能需要获取客户端和服务端的底层传输信息,spring-ws中有一个传输上下文,及TransportContext
,从TransportContext
中可以获取底层的WebServiceConnection
(在服务端实质上一般是HttpServletConnetion
,客户端一般是HttpUrlConnection
或者CommonsHttpConnection
)。
比如在服务端想获取请求的IP地址,可以如下方式:
TransportContext context = TransportContextHolder.getTransportContext();
HttpServletConnection connection = (HttpServletConnection )context.getConnection();
HttpServletRequest request = connection.getHttpServletRequest();
String ipAddress = request.getRemoteAddr();
消息记录与跟踪
保证Commons Logging是1.1或以上版本,不要集成Log4J的TRACE级别。
服务端:
org.springframework.ws.server.MessageTracing
org.springframework.ws.server.MessageTracing.sent
org.springframework.ws.server.MessageTracing.received
客户端:
org.springframework.ws.client.MessageTracing.sent
org.springframework.ws.client.MessageTracing.received
log4j.properties
log4j.rootCategory=INFO, stdout
log4j.logger.org.springframework.ws.client.MessageTracing.sent=TRACE
log4j.logger.org.springframework.ws.client.MessageTracing.received=DEBUG
log4j.logger.org.springframework.ws.server.MessageTracing=DEBUG
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%p [%c{3}] %m%n
其他文章
spring web service 2
spring web service 3
maven配置文件settings.xml详解
Nginx转发请求过程解析
Nginx中的负载均衡算法
Nginx upstream指令配置说明
Nginx中虚拟服务器server指令配置说明
Nginx中proxy_pass/proxy_redirect/proxy_set_header配置说明
Nginx中ngx_http_core_module相关指令配置说明
Java自带JVM监控工具jstat使用详细说明
Java自带JVM监控工具jps使用详细说明
Java自带故障分析工具jmap工具使用说明
Java自带故障分析工具jhat工具使用说明
网友评论