美文网首页
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