在经过前面两篇文章的学习,我已经能够熟练创建一个正常运行的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")。所需元素为<{}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...,有需要的可以下载下来看看。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。