本文共 10013 字,大约阅读时间需要 33 分钟。
Spring Boot整合web service
之前用webservice的CXF框架,
很方便与简洁,但是悲催的是在部署到生产环境的WebSphere(was平台)下后,不能正常运行.
网上一查,原来WebSphere和CXF的冲突问题由来已久,解决方案也五花八门,会有不必要的麻烦.既然如此趁项目的web service还在刚整合阶段,换个组件吧.
问了其它项目组同事以前是怎么实现的,说就是因为冲突问题以前都是采用了httpClient之类的组装xml发送原生http请求调用的.
处理方式欠妥,作者当然不能接受。既然spring能在WebSphere下正常运行,那么spring的组件能够成功运行的可能性相对较大。
研究考虑之后,决定选用spring-ws来实现webservice。事实证明选择是正确的。
spring-boot相关项目源码,
码云地址:
github地址:
spring-ws的资料相对较少,不像cxf那样一找就是一大堆,不过好在有官方示例和文档。
官方示例中使用了spring boot,这跟我当前的环境不谋而合,不过它示例了多个构建工具和Groovy等,看起来比较复杂难懂一些,这里我们就以单纯的maven来实现。
新建项目:ws-server(源码地址:
码云地址:
github地址:
)
spring boot的工程,除了spring boot外还需要添加spring-ws和wsdl4j的依赖,当然后面生成代码还需要添加maven的jaxb2插件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-ws</artifactId>
<version>1.4.5.RELEASE</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
</dependency>
spring-ws的发布,都是以一个schema文件(xsd)定义开始的,它描述了webservice 的参数以及返回的数据。
这是官方示例给出的countries.xsd,这里以它为例,更改下命名空间,因为jaxb2插件自动生成代码是以命名空间来确定包名的。手动生成不影响!
<xs:schemaxmlns:xs="http://www.w3.org/2001/XMLSchema"xmlns:tns="http://www.wyait.com/ws"
targetNamespace="http://www.wyait.com/ws"elementFormDefault="qualified">
<xs:elementname="getCountryRequest">
<xs:complexType>
<xs:sequence>
<xs:elementname="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:elementname="getCountryResponse">
<xs:complexType>
<xs:sequence>
<xs:elementname="country" type="tns:country"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexTypename="country">
<xs:sequence>
<xs:elementname="name" type="xs:string"/>
<xs:elementname="population" type="xs:int"/>
<xs:elementname="capital" type="xs:string"/>
<xs:elementname="currency" type="tns:currency"/>
</xs:sequence>
</xs:complexType>
<xs:simpleTypename="currency">
<xs:restrictionbase="xs:string">
<xs:enumerationvalue="GBP"/>
<xs:enumerationvalue="EUR"/>
<xs:enumerationvalue="PLN"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
pom文件配置jaxb2插件:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<schemaDirectory>${project.basedir}/src/main/resources/schema</schemaDirectory>
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
<clearOutputDir>false</clearOutputDir>
</configuration>
</plugin>
eclipse开发工具:选中countries.xsd文件,右键:
IDEA开发工具:
对coruntries.xsd右键,然后选中web service那一项,generatejava code from xml schema using jaxb
选择要生成代码的包位置!生成代码结果:
我们就不再像spring-ws官方那样再建一个Repository了,这里直接返回。需要注意PayloadRoot注解当中的namespace和localPart需要和xsd中对应。
/**
*
* @项目名称:ws-server
* @类名称:CountryEndPoint
* @类描述:编写endpoint
* @创建人:wyait
* @创建时间:2017年7月14日上午11:02:49
* @version:
*/
@Endpoint
public class CountryEndPoint {
privatestatic final String NAMESPACE_URI = "http://www.wyait.com/ws";
@PayloadRoot(namespace= NAMESPACE_URI, localPart = "getCountryRequest")
@ResponsePayload
publicGetCountryResponse getCountry(
@RequestPayloadGetCountryRequest request) {
GetCountryResponseresponse = new GetCountryResponse();
Countrypoland = new Country();
poland.setName("Poland-"+ request.getName());
poland.setCapital("Warsaw");
poland.setCurrency(Currency.PLN);
poland.setPopulation(38186860);
response.setCountry(poland);
returnresponse;
}
}
/**
*
* @项目名称:ws-server
* @类名称:WebServiceConfig
* @类描述:spring boot整合web service
* @创建人:wyait
* @创建时间:2017年7月14日上午11:24:22
* @version:
*/
@EnableWs
@Configuration
public class WebServiceConfigextends WsConfigurerAdapter {
@Bean
publicServletRegistrationBean messageDispatcherServlet(
ApplicationContextapplicationContext) {
MessageDispatcherServletservlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
returnnew ServletRegistrationBean(servlet, "/ws/*");
}
@Bean(name= "countries")
publicDefaultWsdl11Definition defaultWsdl11Definition(
XsdSchemacountriesSchema) {
DefaultWsdl11Definitionwsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("CountriesPort");
wsdl11Definition.setSchema(countriesSchema);
returnwsdl11Definition;
}
@Bean
publicXsdSchema countriesSchema() {
returnnew SimpleXsdSchema(
newClassPathResource("schema/countries.xsd"));
}
}
到这里spring-ws的所有配置和工作都已经完成了,上面的DefaultWsdl11Definitionid默认就是发布的ws的访问路径。
启动后访问 http://localhost:8080/ws/countries.wsdl 发现web service已经成功发布了。
这里要注意一下spring-ws发布的webservice是以后缀.wsdl访问的,跟传统的?wsdl不大一样,也看过它的源码,发现是在判断后缀时写死的,所以没办法配置修改了。
还有就是spring-ws实际上把发布wsdl和真正的服务实现Endpoint分开了,如果你的Endpoint不正确,很可能会出现浏览器访问.wsdl地址看起来正常而客户端调用却出现Not Found 404的错误。
前面我们已经整合spring-ws实现了webservice的服务端:Spring Boot整合spring-ws开发web service客户端
接下来就是实现客户端进行调用了。
新建:ws-client(项目源码:)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-ws</artifactId>
<version>1.4.5.RELEASE</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
</dependency>
服务端由一个xsd文件开始,客户端则是由一个wsdl文件开始。
获取wsdl文件也十分简单,用浏览器访问webservice地址,然后另存为即可。当然也可以直接用url地址来生成代码。
方式一:本地另存:
完整的wsdl文件如下:
<wsdl:definitionsxmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:sch="http://www.wyait.com/ws"xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://www.wyait.com/ws"targetNamespace="http://www.wyait.com/ws">
<wsdl:types>
<xs:schemaxmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"targetNamespace="http://www.wyait.com/ws">
<xs:elementname="getCountryRequest">
<xs:complexType>
<xs:sequence>
<xs:elementname="name" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:elementname="getCountryResponse">
<xs:complexType>
<xs:sequence>
<xs:elementname="country" type="tns:country" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexTypename="country">
<xs:sequence>
<xs:elementname="name" type="xs:string" />
<xs:elementname="population" type="xs:int" />
<xs:elementname="capital" type="xs:string" />
<xs:elementname="currency" type="tns:currency" />
</xs:sequence>
</xs:complexType>
<xs:simpleTypename="currency">
<xs:restrictionbase="xs:string">
<xs:enumerationvalue="GBP" />
<xs:enumerationvalue="EUR" />
<xs:enumerationvalue="PLN" />
</xs:restriction>
</xs:simpleType>
</xs:schema>
</wsdl:types>
<wsdl:messagename="getCountryResponse">
<wsdl:partelement="tns:getCountryResponse" name="getCountryResponse"></wsdl:part>
</wsdl:message>
<wsdl:messagename="getCountryRequest">
<wsdl:partelement="tns:getCountryRequest"name="getCountryRequest"></wsdl:part>
</wsdl:message>
<wsdl:portTypename="CountriesPort">
<wsdl:operationname="getCountry">
<wsdl:inputmessage="tns:getCountryRequest"name="getCountryRequest"></wsdl:input>
<wsdl:outputmessage="tns:getCountryResponse"name="getCountryResponse"></wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:bindingname="CountriesPortSoap11" type="tns:CountriesPort">
<soap:bindingstyle="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operationname="getCountry">
<soap:operationsoapAction="" />
<wsdl:inputname="getCountryRequest">
<soap:bodyuse="literal" />
</wsdl:input>
<wsdl:outputname="getCountryResponse">
<soap:bodyuse="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:servicename="CountriesPortService">
<wsdl:portbinding="tns:CountriesPortSoap11"name="CountriesPortSoap11">
<soap:address/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
方式二:jaxb2插件配置生成wsdl文件
跟服务端根据xsd来生成代码类似,客户端同样可以根据wsdl来生成代码。maven插件依赖:
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.12.3</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<schemaLanguage>WSDL</schemaLanguage>
<generatePackage>com.wyait.ws.domain</generatePackage>
<generateDirectory>${basedir}/src/main/java</generateDirectory>
<schemas>
<schema>
<fileset>
<!--Defaults to schemaDirectory. -->
<directory>${basedir}/src/main/resources/schema</directory>
<!--Defaults to schemaIncludes. -->
<includes>
<include>*.wsdl</include>
</includes>
<!--Defaults to schemaIncludes -->
<!--<excludes>-->
<!--<exclude>*.xs</exclude>-->
<!--</excludes>-->
</fileset>
<!--<url>http://localhost:8080/ws/countries.wsdl</url>-->
</schema>
</schemas>
</configuration>
</plugin>
配置完,install将生成客户端代码。这里生成的代码跟我们前面发布的服务端代码应该是一样的,当然包名可能不同这个由自己指定。
在生成代码的同时会生成META-INF文件夹,这个可以移到resources目录下或者直接删除都没有关系。生成后的项目结构图:
编写ws 客户端代码:
public class WsClient extendsWebServiceGatewaySupport {
publicGetCountryResponse getCountry(String name) {
GetCountryRequestrequest = new GetCountryRequest();
request.setName(name);
GetCountryResponseresponse = (GetCountryResponse) getWebServiceTemplate()
.marshalSendAndReceive(
"http://http://127.0.0.1:9111/ws/countries.wsdl",
request);
returnresponse;
}
}
编写完一切代码之后,同样需要配置到spring boot才行,ContextPath指定刚才生成代码所在的包名,它会到该包下去寻找相应的类自动进行数据转换:
@Configuration
public class WSConfig {
@Bean
publicJaxb2Marshaller marshaller() {
Jaxb2Marshallermarshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.wyait.ws.domain");
returnmarshaller;
}
@Bean
publicWsClient wsClient(Jaxb2Marshaller marshaller) {
WsClientclient = new WsClient();
client.setDefaultUri("http://127.0.0.1:9111/ws/countries.wsdl");
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
returnclient;
}
}
使用了RestController,直接将调用ws返回的数据用json格式输出到页面。
@RestController
public class IndexController {
@Autowired
private WsClient wsClient;
@RequestMapping("callws")
public Object callWs() {
GetCountryResponse response =wsClient.getCountry("hello");
return response.getCountry();
}
}
端口改为:9112 避免和服务端端口9111冲突
启动,访问:http://127.0.0.1:9112/callws
回顾1.6章节末尾:
“还有就是spring-ws实际上把发布wsdl和真正的服务实现Endpoint分开了,如果你的Endpoint不正确,很可能会出现浏览器访问.wsdl地址看起来正常而客户端调用却出现Not Found 404的错误。”
排查ws服务端endpoint代码,没问题。是包命名的时候,单词写错了:
将wyati改成:wyait。启动,访问:http://127.0.0.1:9112/callws
本文转自 wyait 51CTO博客,原文链接:http://blog.51cto.com/wyait/1970033,如需转载请自行联系原作者