1

在经过前面两篇文章的学习,我已经能够熟练创建一个正常运行的spring-ws的webservice服务,大多数接口,都是要有返回数据,所以这篇文章就是学习spring-ws怎么实现返回数据

实现一个常规的返回数据

一个接口返回的数据一般都是对象,那就看看怎么返回一个对象数据。

首先,还是要看spring-ws文档的,在参考文档端点一节,在这一节中,开始就提出了,要使用@ResponsePayload注解,实现返回数据。支持类型如下:

支持类型

从上面表格可以看出,spring-ws的得demo中,使用的是jdom类型的,先不管其它类型,在此基础上,实现一个返回值,

修改代码:
加上注解

@PayloadRoot(namespace = NAMESPACE_URI, localPart = "HolidayRequest")
    @ResponsePayload
    public Dog handleHolidayRequest(@RequestPayload Element holidayRequest) throws Exception {
    
        System.out.println("请求进来了");
        
        Date startDate = parseDate(startDateExpression, holidayRequest);
        Date endDate = parseDate(endDateExpression, holidayRequest);
        String name = firstNameExpression.evaluateFirst(holidayRequest).getText() + " " + lastNameExpression.evaluateFirst(holidayRequest).getText();
        
        humanResourceService.bookHoliday(startDate, endDate, name);
        
        Dog dog = new Dog("中华田园犬", 5);
        
        return dog;
    }

第一个返回类:

public class Dog {

    private String name;
    private Integer age;
    
    public Dog (String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName () {
        return name;
    }
    
    public void setName (String name) {
        this.name = name;
    }
    
    public Integer getAge () {
        return age;
    }
    
    public void setAge (Integer age) {
        this.age = age;
    }

启动测试,soap ui 测试结果:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <SOAP-ENV:Body>
      <SOAP-ENV:Fault>
         <faultcode>SOAP-ENV:Server</faultcode>
         <faultstring xml:lang="en">No adapter for endpoint [public com.nyl.learn.hr.model.Dog com.nyl.learn.hr.ws.HolidayEndpoint.handleHolidayRequest(org.jdom2.Element) throws java.lang.Exception]: Is your endpoint annotated with @Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?</faultstring>
      </SOAP-ENV:Fault>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

后台输出结果:

六月 19, 2017 6:08:13 下午 org.springframework.ws.transport.http.MessageDispatcherServlet initServletBean
信息: FrameworkServlet 'spring-ws': initialization completed in 502 ms

没有输出信息,可以看出请求根本没有进来

从错误信息,可以看到适配到返回值为Dog的端点,这么说来,肯定是我配错了。

可是spring-ws的参考文档上也没说需要给Dog加注解或者实现某个类。
重新打开新生成wsdl文件,发现是这样的:

wsdl:portType name="HumanResource">
<wsdl:operation name="Holiday">
<wsdl:input message="tns:HolidayRequest" name="HolidayRequest"></wsdl:input>
</wsdl:operation>
</wsdl:portType>

也就是说,spring-ws会查找名字为Holiday参数为HolidayRequest,返回值为void的方法,所以找不到我写的,可是按理说,我既然写返回值了,为什么生成还没有返回值的port。
我突然想到,上面写的那个返回值支持类型,我写的dog好像不是表格中任何一种类型,因为我只对jaxb2有点印象,所以就按照jaxb2类型修改,修改如下:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "dog")
public class Dog {

    
    private String name;
    private Integer age;
    
    public Dog () {
    }
    
    public Dog (String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName () {
        return name;
    }
    
    public void setName (String name) {
        this.name = name;
    }
    
    public Integer getAge () {
        return age;
    }
    
    public void setAge (Integer age) {
        this.age = age;
    }
}

相比前面,多了两个注释和一个空构造方法,先不解释,看一下测试结果:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <SOAP-ENV:Body>
      <dog>
         <name>中华田园犬</name>
         <age>5</age>
      </dog>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

总而言之,现在得到返回值了。

因为我用的是jdk自带的jaxb2,所以不需要增加额外的jar包就可以使用,这个jar包提供了一下几个注释用于xml和对象的转化,我从网上找了一段感觉非常不错的说明:

JAXBContext类,是应用的入口,用于管理XML/Java绑定信息。
Marshaller接口,将Java对象序列化为XML数据。
Unmarshaller接口,将XML数据反序列化为Java对象。
 
@XmlType,将Java类或枚举类型映射到XML模式类型
@XmlAccessorType(XmlAccessType.FIELD) ,控制字段或属性的序列化。FIELD表示JAXB将自动绑定Java类中的每个非静态的(static)、非瞬态的(由@XmlTransient标 注)字段到XML。其他值还有XmlAccessType.PROPERTY和XmlAccessType.NONE。
@XmlAccessorOrder,控制JAXB 绑定类中属性和字段的排序。
@XmlJavaTypeAdapter,使用定制的适配器(即扩展抽象类XmlAdapter并覆盖marshal()和unmarshal()方法),以序列化Java类为XML。
@XmlElementWrapper ,对于数组或集合(即包含多个元素的成员变量),生成一个包装该数组或集合的XML元素(称为包装器)。
@XmlRootElement,将Java类或枚举类型映射到XML元素。
@XmlElement,将Java类的一个属性映射到与属性同名的一个XML元素。
@XmlAttribute,将Java类的一个属性映射到与属性同名的一个XML属性。

原文是个博客,地址为这里,有兴趣的可以看看。

此时,再次查看生成的wsdl文件,发现并不是我想象中的样子:

···

<wsdl:portType name="HumanResource">
   <wsdl:operation name="Holiday">
     <wsdl:input message="tns:HolidayRequest" name="HolidayRequest"></wsdl:input>
   </wsdl:operation>
</wsdl:portType>

···

soap UI 测试都返回数据了,为什么wsdl文件上却没有返回值,不知道该搜索什么,只好去看spring-ws的参考文档,最终在这一节找到了答案:

The DefaultWsdl11Definition (and therefore, the <dynamic-wsdl> tag) builds a WSDL from a XSD schema by using conventions. It iterates over all element elements found in the schema, and creates a message for all elements. Next, it creates WSDL operation for all messages that end with the defined request or response suffix. The default request suffix is Request; the default response suffix is Response, though these can be changed by setting the requestSuffix and responseSuffix attributes on <dynamic-wsdl />, respectively. It also builds a portType, binding, and service based on the operations.

谷歌翻译成中文是:

DefaultWsdl11Definition(因此,<dynamic-wsdl>标记)通过使用约定从XSD模式构建WSDL。
它迭代在模式中找到的所有元素元素,并为所有元素创建一条消息。 接下来,它为所有以定义的请求或响应后缀结尾的消息创建WSDL操作。
默认请求后缀为Request; 默认响应后缀为Response,但可以通过在<dynamic-wsdl
/>上分别设置requestSuffix和responseSuffix属性来更改。 它还基于操作构建portType,绑定和服务。

也就是说,只有带有指定的后缀,才能正确的生成wsdl文件,那么再改一下代码,如下:

1、把Dog类改成DogResponse

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "DogResponse")
@XmlType(name = "", propOrder = {
        "name","age"
})
public class DogResponse {
    
    @XmlElement(required = true)
    protected String name;
    
    protected Integer age;
    
    
    
    public String getName () {
        return name;
    }
    
    public void setName (String name) {
        this.name = name;
    }
    
    public Integer getAge () {
        return age;
    }
    
    public void setAge (Integer age) {
        this.age = age;
    }
}

2、xsd修改

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://mycompany.com/hr/webservice"
           targetNamespace="http://mycompany.com/hr/webservice" elementFormDefault="qualified">


    <xs:element name="HolidayRequest">
        <xs:complexType>
            <xs:all>
                <xs:element name="name" type="xs:string"/>
            </xs:all>
        </xs:complexType>
    </xs:element>

    <xs:element name="DogResponse">
        <xs:complexType>
            <xs:all>
                <xs:element name="name" type="xs:string"/>
            </xs:all>
        </xs:complexType>
    </xs:element>


</xs:schema>

3、现在的HolidayEndpoint是这样的:

@PayloadRoot(namespace = NAMESPACE_URI, localPart = "holidayRequest")
    @ResponsePayload
    public DogResponse handleHolidayRequest(@RequestPayload HolidayRequest holidayRequest) throws Exception {
    
        System.out.println("请求进来了");
    
    
        DogResponse dogResponse = new DogResponse();
        dogResponse.setName("中华田园犬");
        dogResponse.setAge(5);
        
        return dogResponse;
    }

重启测试,新生成的wsdl结果如下:

<wsdl:portType name="HumanResource">
   <wsdl:operation name="Holiday">
      <wsdl:input message="tns:HolidayRequest" name="HolidayRequest"></wsdl:input>
   </wsdl:operation>
   <wsdl:operation name="Dog">
      <wsdl:output message="tns:DogResponse" name="DogResponse"></wsdl:output>
   </wsdl:operation>
</wsdl:portType>

soap ui 测试结果仍然是对的,生成的wsdl仍然是错的;

继续修改,把DogResponse,改成HolidayResponse,修改后,重新测试,结果如下:

<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文件了,现在我把HolidayRequest和HolidayResponse都改成DogRequest和DogResponse,再测试一下,结果如下:

<wsdl:portType name="HumanResource">
  <wsdl:operation name="Dog">
     <wsdl:input message="tns:DogRequest" name="DogRequest"></wsdl:input>
     <wsdl:output message="tns:DogResponse" name="DogResponse"></wsdl:output>
  </wsdl:operation>
</wsdl:portType>

可以看出,请求和返回的名字是有要求的,两个名字前面要一样,后缀分别是固定的配置,默认为Request和Response;

把名字改回HolidayRequest和HolidayResponse, 现在生成wsdl没有错误了,那继续测试soap ui。

但是很不幸是,soap ui测试无法通过了,因为生成的service是这样的:

<wsdl:service name="HumanResourceService">
  <wsdl:port binding="tns:HumanResourceSoap11" name="HumanResourceSoap11">
    <soap:address location="/holidayService/"/>
  </wsdl:port>
</wsdl:service>

测试时,认为我的访问路径是/holidayService/,这样肯定是无法访问的,因为这个location是相对路径,需要动态转化为真实路径,需要在web.xml中配置下面的init内容,如下:

<servlet>
    <servlet-name>spring-ws</servlet-name>
    <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
    <init-param>
      <param-name>transformWsdlLocations</param-name>
      <param-value>true</param-value>
    </init-param>
  </servlet>

应该是我改代码的时候,误删了。加回来继续测试,测试请求如下:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://mycompany.com/hr/webservice">
   <soapenv:Header/>
   <soapenv:Body>
      <web:holidayRequest>
         <web:name>旺财</web:name>
      </web:holidayRequest>
   </soapenv:Body>
</soapenv:Envelope>

返回的信息是:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <SOAP-ENV:Body>
      <SOAP-ENV:Fault>
         <faultcode>SOAP-ENV:Server</faultcode>
         <faultstring xml:lang="en">意外的元素 (uri:"http://mycompany.com/hr/webservice", local:"holidayRequest")。所需元素为&lt;{}holidayRequest></faultstring>
      </SOAP-ENV:Fault>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

明显可以看出报错了,百度了一下,看到有人说,要在请求参数上,所有的请求参数的元素加上namespace,于是改成下面这个样子:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
        "name"
})
@XmlRootElement(name = "holidayRequest", namespace = "http://mycompany.com/hr/webservice")
public class HolidayRequest {
    
    @XmlElement(namespace = "http://mycompany.com/hr/webservice")
    protected String name;
    
    public String getName () {
        return name;
    }
    
    public void setName (String name) {
        this.name = name;
    }
}

重新测试,结果如下:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <SOAP-ENV:Body>
      <holidayResponse>
         <name>中华田园犬</name>
         <age>5</age>
      </holidayResponse>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

总结

刚开始觉得挺简单的,下载下来spring-ws的demo,非常顺利的运行起来了,可是自己搞一遍的时候,错误百出,网上搜索的话,大多是求解决方法的,很少有提出有用的解决方案的,在上面,我把自己的解决过程写了出来,希望对和我一样刚开始学这个东西的朋友有点帮助。

总得来讲,经过这一段闲暇时间的学习对webservice相关的概念,对spring-ws相关的配置,算是有了比较直观充分地了解。

这是我的最终修改的项目,发布出来的的路径,测试wsdl链接

源码,我放到码云上了,路径为:https://git.oschina.net/thewa...,有需要的可以下载下来看看。


流浪的神明
226 声望9 粉丝