美文网首页
spring web service 1

spring web service 1

作者: Elf_乐易 | 来源:发表于2020-03-04 14:47 被阅读0次

因为工作需要,在基本没这么玩过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整体结构图

spring-deps.png

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步骤

  1. 以一个标准的方式开始,并引入存在的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>
    
  2. 定义message,message可以有多个

     <wsdl:message name="HolidayRequest">
            <wsdl:part element="schema:HolidayRequest" name="HolidayRequest"/>
        </wsdl:message>
    
  3. 将message作为一个动作加入到port type中

      <wsdl:portType name="HumanResource">
            <wsdl:operation name="Holiday">
                <wsdl:input message="tns:HolidayRequest" name="HolidayRequest"/>
            </wsdl:operation>
        </wsdl:portType>
    
  4. binding定义客户端如何引用动作

  5. 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.Sourcejavax.xml.transform.Result以及一个写出消息流的方法。

public interface WebServiceMessage {
    Source getPayloadSource();

    Result getPayloadResult();

    void writeTo(OutputStream var1) throws IOException;
}

XML的操作方式有很多种,不通操作对SourceResult的包装如下所示:

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

SoapMessageWebServiceMessage的实现类,包含了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的对象。

SaajSoapMessageFactorAxiomSoapMessageFactory都有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工具使用说明

相关文章

网友评论

      本文标题:spring web service 1

      本文链接:https://www.haomeiwen.com/subject/jcbvlhtx.html