背景:有的系统还用soap协议来做webservice.特别要和他们对接的时候。我们需要实现一套。 今天说说,利用spring-ws来(部署,调用)webservcie,能很好的和主流架构(spring-mvc)结合。 参考资料,官方文档https://docs.spring.io/spring-ws/docs/3.0.0.RELEASE/reference/
spring-ws像spring-mvc一样,在集成到web项目时,前端有个servlet分发请求消息的概念。 这个servlet接受soap消息,通过映射转发到后端的服务实现类方法中(Endpiont) 在请求进来处理过程中,可以添加,拦截器(Interceptor),异常处理器(ExceptionResolver)。 通过拦截器可以做一些额外的定制功能,比如安全。通过异常处理器定制异常信息显示,处理等。
一个soap消息进来的处理流程图如下:
依赖的jar: 官方给出的依赖jar关系图:
最新版,需要jdk1.8.
我在jdk1.7用的包依赖版本
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-ws-core</artifactId>
<version>2.4.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-xml</artifactId>
<version>2.4.0.RELEASE</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.3</version>
</dependency>
没有用spring-oxm
1,配置servlet web.xml文件里添加servlet 拦截webservice消息请求。
<servlet>
<servlet-name>spring-ws</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>spring-ws</servlet-name>
<!--webservice拦截路径-->
<url-pattern>/webservice/*</url-pattern>
</servlet-mapping>
2,定义,发布webservice服务,具体定义服务操作和操作的请求返回的xml格式。这里我们在/WEB-INF/hr.xsd目录下,编写好xsd文件。
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:hr="http://j2eeweb.wannshan.cn/hr/schemas"
elementFormDefault="qualified"
targetNamespace="http://j2eeweb.wannshan.cn/hr/schemas">
<!--按约定,每个operationNameRequest格式的element每个对应一个operation,同时operationNameRequest是请求参数元素名称-->
<!--同理 operationNameResponse 格式的element是对应一个operation的返回参数-->
<xs:element name="saveCountryRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="country" type="hr:country"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="saveCountryResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<!--getCountry操作,请求返回值定义-->
<xs:element name="getCountryRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="getCountryResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="country" type="hr:country"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="country">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="population" type="xs:int"/>
<xs:element name="capital" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
通过xsd文件我们定义个两个操作(方法)和每个方法的请求和返回格式 到这里我们虽然还没有服务实现,但可以以wsdl形式发布服务了。具体:
在WEB-INF目录下,新建spring-ws-servlet.xml文件([servletName-servlet.xml]规则)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:sws="http://www.springframework.org/schema/web-services"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!--动态生成wsdl文件url http://host:port/webservice/queryService/queryContry.wsdl id值是wsdl文件路径-->
<sws:dynamic-wsdl id="queryContry"
portTypeName="query"
locationUri="/webservice/queryService/"
targetNamespace="http://j2eeweb.wannshan.cn/hr/schemas">
<!--hr.xsd路径-->
<sws:xsd location="/WEB-INF/hr.xsd"/>
</sws:dynamic-wsdl>
<!--静态wsdl 指定文件 一般现有动态生成,最后上生产考到静态文件中去-->
<!--<sws:static-wsdl id="queryContry" location="/WEB-INF/queryContry.wsdl"/>-->
</beans>
这里说下,或许我们不太会编写wsdl文件,上面的配置文件里,spring-ws给我们提供了一种动态生成wsdl的机制。 因为动态生成有缓存,可以在生产环境上配置静态wsdl,测试环境用动态。 然后启动web服务。
3,编写webservice服务实现类(Endpoint)完成具体的服务业务
编写前,我们可以用maven-jaxb2-plugin插件根据wsdl文件生成业务请求对象类 GetCountryRequest,GetCountryRequest,SaveCountryResponse,GetCountryResponse,Country 里面有jaxb绑定注解。具体在项目pom.xml文件里配置
<build>
<plugins>
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.13.1</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<schemaLanguage>WSDL</schemaLanguage>
<!--pojo存放包名-->
<generatePackage>cn.wannshan.j2ee.ws.dto</generatePackage>
<schemas>
<schema>
<url>http://localhost:8080/webservice/queryService/queryContry.wsdl</url>
</schema>
</schemas>
</configuration>
</plugin>
</plugins>
</build>
然后对项目执行 maven package 任务
类生成好后,可以编写webservice实现类了,我自己的实现,如下:
package cn.wannshan.j2ee.ws;
import cn.wannshan.j2ee.ws.dto.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
/**
* xxx
*
*/
@Endpoint
public class CountryQuery {
private static final String NAMESPACE_URI = "http://j2eeweb.wannshan.cn/hr/schemas";
//注入spring bean
@Autowired
CountryRepository countryRepository;
/***
* 查询country
* @param request
* @return
* @throws Exception
*/
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCountryRequest")
@ResponsePayload
public GetCountryResponse queryCountry(@RequestPayload GetCountryRequest request) throws Exception {
GetCountryResponse response = new GetCountryResponse();
response.setCountry(countryRepository.findCountry(request.getName()));
return response;
}
/**
* 保存country
* @param request
* @return
* @throws Exception
*/
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "saveCountryRequest")
@ResponsePayload
public SaveCountryResponse saveCountry(@RequestPayload SaveCountryRequest request) throws Exception {
SaveCountryResponse response = new SaveCountryResponse();
countryRepository.putCounttry(request.getCountry());
response.setName(request.getCountry().getName());
return response;
}
}
//CountryRepository 类,简单利用map在内存中保存数据
package cn.wannshan.j2ee.ws;
import cn.wannshan.j2ee.ws.dto.Country;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
/**
*
*
*/
@Component
public class CountryRepository {
private static final Map<String, Country> countries = new HashMap();
@PostConstruct
public void initData() {
Country spain = new Country();
spain.setName("Spain");
spain.setCapital("Madrid");
spain.setPopulation(46704314);
countries.put(spain.getName(), spain);
Country poland = new Country();
poland.setName("Poland");
poland.setCapital("Warsaw");
poland.setPopulation(38186860);
countries.put(poland.getName(), poland);
Country uk = new Country();
uk.setName("United Kingdom");
uk.setCapital("London");
uk.setPopulation(63705000);
countries.put(uk.getName(), uk);
}
public String putCounttry(Country country){
Assert.notNull(country, "The country must not be null");
Assert.notNull(country.getName(), "The country's name must not be null");
countries.put(country.getName(),country);
return country.getName();
}
public Country findCountry(String name) {
Assert.notNull(name, "The country's name must not be null");
return countries.get(name);
}
}
这里简单说下,CountryQuery 类:
类要用@Endpoint注解,表明它是spring-ws认可的webservice服务类。
类中两个方法,一个查询country,一个保存新的country. 两个方法上都用到了注解 @PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCountryRequest") @ResponsePayload
@PayloadRoot:实现soap消息的映射路由功能,简单点说,就是soap消息里,namespace = NAMESPACE_URI并且有getCountryRequest元素节点的,才能由这个方法接受处理。 @ResponsePayload:表示这个方法有返回值,并且会把返回值封装格式化为soap消息格式。
而方法名参数上用到了@RequestPayload GetCountryRequest request,表示soap消息的,有效负载,映射到参数request对象上。
以上都是spring-ws自动帮你做好的。
3,部署webservice服务实现
实现类做好后,在spring-ws-servlet.xml文件文件里加入如下配置,重启web服务。就可以接受处理合适的webservice soap消息请求了。 <!--webservice实现类 endpoint 所在包--> <context:component-scan base-package="cn.wannshan.j2ee.ws"></context:component-scan> <!--开启sws扫描,beans里要加入xmlns:sws命名空间--> <sws:annotation-driven/>
以上是webservcie服务部署过程。
spring-ws还提供了webservcie服务客户端类,用于请求soap webservice叫WebServiceTemplate。只要在spring文件里配置一个bean
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
<constructor-arg ref="messageFactory"/>
<!--webserivce服务地址-->
<property name="defaultUri" value="http://localhost:8080/webservice/queryService"/>
</bean>
然后就可以用了 下面是我写的测试类片段
@Test
public void testWebservice() throws JAXBException {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
//这是个包名,是你利用maven插件根据xsd文件生成pojo类,存放包名
marshaller.setContextPath("cn.wannshan.j2ee.ws.dto");
//指定Jaxb方案实现类。spring提供Jaxb2Marshaller
webServiceTemplate.setMarshaller(marshaller);
webServiceTemplate.setUnmarshaller(marshaller);
//查询Country
GetCountryRequest getCountryRequest=new GetCountryRequest();
getCountryRequest.setName("Spain");
GetCountryResponse getCountryResponse= (GetCountryResponse) webServiceTemplate.marshalSendAndReceive(getCountryRequest);
Assert.assertEquals(getCountryResponse.getCountry().getName(), "Spain");
//保存Country
Country country=new Country();
country.setName("中国");
country.setCapital("北京");
country.setPopulation(1400000000);
SaveCountryRequest saveCountryRequest=new SaveCountryRequest();
saveCountryRequest.setCountry(country);
SaveCountryResponse saveCountryResponse= (SaveCountryResponse) webServiceTemplate.marshalSendAndReceive(saveCountryRequest);
Assert.assertEquals(saveCountryResponse.getName(), "中国");
}