Web Service,即“Web 服務”,簡寫為 WS(不是WebSocket哦),從字面上理解,它其實就是“基於 Web 的服務”。而服務卻是雙方的,有服務需求方,就有服務提供方。服務提供方對外發布服務,服務需求方呼叫服務提供方所釋出的服務。
現階段,國內網際網路企業使用spring boot、spring cloud較多,而這兩者都是基於http通訊的。但webservice仍在銀行、保險、金融機構等相關企業大量使用。那在與這些傳統企業對接時,spring boot 如何整合webservice將是一個亟需解決的問題。
目前較為方便的整合方案主要有apache提供的CXF以及Spring 自己提供的Spring-WS
Apache CXF -- WS-Security
Spring Web Services
本篇教材主要是讓大家快速上手cxf,實現基於Spring Boot 的webservice開發。
專案原始碼: https://github.com/ouyushan/spring-webservice-samples
spring-cxf參考原始碼: https://github.com/code-not-found/cxf-jaxws
spring-ws參考原始碼: https://github.com/code-not-found/spring-ws
二、開發環境ide::IDEA
jdk:1.8
maven:3.6.2
spring boot:2.4.0
cxf:3.4.1
三、實現步驟3.1、新建父工程新建父工程spring-webservice-samples,對所有示例子模組進行統一管理。
spring boot與cxf的簡單整合只需引入cxf-spring-boot-starter-jaxws,該依賴會自動引入web及cxf基礎包。
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxws</artifactId> <version>3.4.1</version></dependency>
3.2、新建子工程新建子工程spring-cxf-client-begin、spring-cxf-server-begin,分別對應webservice 服務的客戶端和服務端。
spring-webservice-samples工程pom檔案
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.ouyushan</groupId> <artifactId>spring-webservice-samples</artifactId> <version>1.0.0.BUILD-SNAPSHOT</version> <packaging>pom</packaging> <name>Spring Web Services Samples</name> <inceptionYear>2020</inceptionYear> <modules> <module>spring-cxf-client-begin</module> <module>spring-cxf-server-begin</module> </modules> <dependencies> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxws</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.11</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.74</version> </dependency> </dependencies></project>
spring-cxf-server-begin服務pom檔案
3.4、服務端釋出服務服務端主要基於User實體類提供使用者查詢等相關功能,然後透過cxf向外釋出介面供客戶端呼叫。
3.4.1、新建實體類Userpackage org.ouyushan.cxf.entity;import java.io.Serializable;/** * @Description: * @Author: ouyushan * @Email: [email protected] * @Date: 2020/11/26 14:42 */public class User implements Serializable { private static final long serialVersionUID = -3628469724795296287L; private String userId; private String userName; private String email; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public User() { } public User(String userId, String userName, String email) { this.userId = userId; this.userName = userName; this.email = email; } @Override public String toString() { return "User{" + "userId='" + userId + '\'' + ", userName='" + userName + '\'' + ", email='" + email + '\'' + '}'; }}
3.4.2、新建UserService介面類package org.ouyushan.cxf.service;import org.ouyushan.cxf.entity.User;import javax.jws.WebMethod;import javax.jws.WebParam;import javax.jws.WebService;/** * @Description: * @Author: ouyushan * @Email: [email protected] * @Date: 2020/11/26 14:42 */@WebService(targetNamespace = "http://ws.cxf.ouyushan.org/")public interface UserService { @WebMethod public String getUserName( /* 指定入參名稱為userId 預設為arg0*/ @WebParam(name = "userId") String userId ); @WebMethod public User getUser( @WebParam(name = "userId") String userId ); @WebMethod public java.util.List<User> getUserList( @WebParam(name = "userId") String userId );}
3.4.3、新建UserServiceImpl服務類package org.ouyushan.cxf.service;import org.ouyushan.cxf.entity.User;import org.springframework.stereotype.Service;import javax.jws.WebService;import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.function.Function;import java.util.stream.Collectors;/** * @Description: * @Author: ouyushan * @Email: [email protected] * @Date: 2020/11/26 14:42 */@Service@WebService(serviceName = "UserService", targetNamespace = "http://ws.cxf.ouyushan.org/", endpointInterface = "org.ouyushan.cxf.service.UserService")public class UserServiceImpl implements UserService{ private static User user1; private static User user2; private static User user3; private static List<User> userList = new ArrayList<>(); private static Map<String, User> userMap; static { user1 = new User("1", "tom", "[email protected]"); user2 = new User("2", "jim", "[email protected]"); user3 = new User("3", "jack", "[email protected]"); userList.add(user1); userList.add(user2); userList.add(user3); userMap = userList.stream().collect(Collectors.toMap(User::getUserId, Function.identity())); } @Override public String getUserName(String userId) { User user = userMap.getOrDefault(userId, new User(userId, "random", "[email protected]")); return user.getUserName(); } @Override public User getUser(String userId) { User user = userMap.getOrDefault(userId, new User(userId, "random", "[email protected]")); return user; } @Override public List<User> getUserList(String userId) { return userList; }}
3.4.4、服務端配置類package org.ouyushan.cxf.config;import org.apache.cxf.Bus;import org.apache.cxf.jaxws.EndpointImpl;import org.apache.cxf.transport.servlet.CXFServlet;import org.ouyushan.cxf.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.web.servlet.ServletRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import javax.xml.ws.Endpoint;/** * @Description: 服務端配置類 * @Author: ouyushan * @Email: [email protected] * @Date: 2020/11/26 14:38 */@Configurationpublic class CxfServerConfig { @Autowired private Bus bus; @Autowired private UserService userService; @Bean public ServletRegistrationBean cxfServlet() { return new ServletRegistrationBean(new CXFServlet(), "/ws/*"); } @Bean public Endpoint userEndpoint() { EndpointImpl endpoint = new EndpointImpl(bus, userService); endpoint.publish("/user"); return endpoint; }}
3.4.5、服務端驗證執行啟動類中的main方法啟動服務
SpringCxfServerBeginApplication
訪問:
http://localhost:8080/ws/
或者直接訪問:
http://localhost:8080/ws/user?wsdl
即可檢視:
This XML file does not appear to have any style information associated with it. The document tree is shown below.<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://ws.cxf.ouyushan.org/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="UserService" targetNamespace="http://ws.cxf.ouyushan.org/"><wsdl:types><xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://ws.cxf.ouyushan.org/" elementFormDefault="unqualified" targetNamespace="http://ws.cxf.ouyushan.org/" version="1.0"><xs:element name="getUser" type="tns:getUser"/><xs:element name="getUserList" type="tns:getUserList"/><xs:element name="getUserListResponse" type="tns:getUserListResponse"/><xs:element name="getUserName" type="tns:getUserName"/><xs:element name="getUserNameResponse" type="tns:getUserNameResponse"/><xs:element name="getUserResponse" type="tns:getUserResponse"/><xs:complexType name="getUserList"><xs:sequence><xs:element minOccurs="0" name="userId" type="xs:string"/></xs:sequence></xs:complexType><xs:complexType name="getUserListResponse"><xs:sequence><xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="tns:user"/></xs:sequence></xs:complexType><xs:complexType name="user"><xs:sequence><xs:element minOccurs="0" name="email" type="xs:string"/><xs:element minOccurs="0" name="userId" type="xs:string"/><xs:element minOccurs="0" name="userName" type="xs:string"/></xs:sequence></xs:complexType><xs:complexType name="getUserName"><xs:sequence><xs:element minOccurs="0" name="userId" type="xs:string"/></xs:sequence></xs:complexType><xs:complexType name="getUserNameResponse"><xs:sequence><xs:element minOccurs="0" name="return" type="xs:string"/></xs:sequence></xs:complexType><xs:complexType name="getUser"><xs:sequence><xs:element minOccurs="0" name="userId" type="xs:string"/></xs:sequence></xs:complexType><xs:complexType name="getUserResponse"><xs:sequence><xs:element minOccurs="0" name="return" type="tns:user"/></xs:sequence></xs:complexType></xs:schema></wsdl:types><wsdl:message name="getUserResponse"><wsdl:part element="tns:getUserResponse" name="parameters"> </wsdl:part></wsdl:message><wsdl:message name="getUser"><wsdl:part element="tns:getUser" name="parameters"> </wsdl:part></wsdl:message><wsdl:message name="getUserNameResponse"><wsdl:part element="tns:getUserNameResponse" name="parameters"> </wsdl:part></wsdl:message><wsdl:message name="getUserList"><wsdl:part element="tns:getUserList" name="parameters"> </wsdl:part></wsdl:message><wsdl:message name="getUserName"><wsdl:part element="tns:getUserName" name="parameters"> </wsdl:part></wsdl:message><wsdl:message name="getUserListResponse"><wsdl:part element="tns:getUserListResponse" name="parameters"> </wsdl:part></wsdl:message><wsdl:portType name="UserService"><wsdl:operation name="getUserList"><wsdl:input message="tns:getUserList" name="getUserList"> </wsdl:input><wsdl:output message="tns:getUserListResponse" name="getUserListResponse"> </wsdl:output></wsdl:operation><wsdl:operation name="getUserName"><wsdl:input message="tns:getUserName" name="getUserName"> </wsdl:input><wsdl:output message="tns:getUserNameResponse" name="getUserNameResponse"> </wsdl:output></wsdl:operation><wsdl:operation name="getUser"><wsdl:input message="tns:getUser" name="getUser"> </wsdl:input><wsdl:output message="tns:getUserResponse" name="getUserResponse"> </wsdl:output></wsdl:operation></wsdl:portType><wsdl:binding name="UserServiceSoapBinding" type="tns:UserService"><soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/><wsdl:operation name="getUserList"><soap:operation soapAction="" style="document"/><wsdl:input name="getUserList"><soap:body use="literal"/></wsdl:input><wsdl:output name="getUserListResponse"><soap:body use="literal"/></wsdl:output></wsdl:operation><wsdl:operation name="getUserName"><soap:operation soapAction="" style="document"/><wsdl:input name="getUserName"><soap:body use="literal"/></wsdl:input><wsdl:output name="getUserNameResponse"><soap:body use="literal"/></wsdl:output></wsdl:operation><wsdl:operation name="getUser"><soap:operation soapAction="" style="document"/><wsdl:input name="getUser"><soap:body use="literal"/></wsdl:input><wsdl:output name="getUserResponse"><soap:body use="literal"/></wsdl:output></wsdl:operation></wsdl:binding><wsdl:service name="UserService"><wsdl:port binding="tns:UserServiceSoapBinding" name="UserServiceImplPort"><soap:address location="http://localhost:8080/ws/user"/></wsdl:port></wsdl:service></wsdl:definitions>
至此服務端部署完成!
3.5、實現客戶端訪問服務客戶端向外提供介面,可透過cxf訪問服務端提供的webservice服務。
3.5.1、生成wsdl檔案訪問服務端:
http://localhost:8080/ws/user?wsdl
將返回結果製作成user.wsdl檔案,並儲存至resources/wsdl目錄下。
將服務端釋出的wsdl檔案內容複製到上述檔案中,並在第一行新增:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
完整user.wsdl
<?xml version="1.0" encoding="UTF-8" standalone="no"?><wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://ws.cxf.ouyushan.org/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="UserService" targetNamespace="http://ws.cxf.ouyushan.org/"><wsdl:types><xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://ws.cxf.ouyushan.org/" elementFormDefault="unqualified" targetNamespace="http://ws.cxf.ouyushan.org/" version="1.0"><xs:element name="getUser" type="tns:getUser"/><xs:element name="getUserList" type="tns:getUserList"/><xs:element name="getUserListResponse" type="tns:getUserListResponse"/><xs:element name="getUserName" type="tns:getUserName"/><xs:element name="getUserNameResponse" type="tns:getUserNameResponse"/><xs:element name="getUserResponse" type="tns:getUserResponse"/><xs:complexType name="getUserList"><xs:sequence><xs:element minOccurs="0" name="userId" type="xs:string"/></xs:sequence></xs:complexType><xs:complexType name="getUserListResponse"><xs:sequence><xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="tns:user"/></xs:sequence></xs:complexType><xs:complexType name="user"><xs:sequence><xs:element minOccurs="0" name="email" type="xs:string"/><xs:element minOccurs="0" name="userId" type="xs:string"/><xs:element minOccurs="0" name="userName" type="xs:string"/></xs:sequence></xs:complexType><xs:complexType name="getUserName"><xs:sequence><xs:element minOccurs="0" name="userId" type="xs:string"/></xs:sequence></xs:complexType><xs:complexType name="getUserNameResponse"><xs:sequence><xs:element minOccurs="0" name="return" type="xs:string"/></xs:sequence></xs:complexType><xs:complexType name="getUser"><xs:sequence><xs:element minOccurs="0" name="userId" type="xs:string"/></xs:sequence></xs:complexType><xs:complexType name="getUserResponse"><xs:sequence><xs:element minOccurs="0" name="return" type="tns:user"/></xs:sequence></xs:complexType></xs:schema></wsdl:types><wsdl:message name="getUserResponse"><wsdl:part element="tns:getUserResponse" name="parameters"> </wsdl:part></wsdl:message><wsdl:message name="getUser"><wsdl:part element="tns:getUser" name="parameters"> </wsdl:part></wsdl:message><wsdl:message name="getUserNameResponse"><wsdl:part element="tns:getUserNameResponse" name="parameters"> </wsdl:part></wsdl:message><wsdl:message name="getUserList"><wsdl:part element="tns:getUserList" name="parameters"> </wsdl:part></wsdl:message><wsdl:message name="getUserName"><wsdl:part element="tns:getUserName" name="parameters"> </wsdl:part></wsdl:message><wsdl:message name="getUserListResponse"><wsdl:part element="tns:getUserListResponse" name="parameters"> </wsdl:part></wsdl:message><wsdl:portType name="UserService"><wsdl:operation name="getUserList"><wsdl:input message="tns:getUserList" name="getUserList"> </wsdl:input><wsdl:output message="tns:getUserListResponse" name="getUserListResponse"> </wsdl:output></wsdl:operation><wsdl:operation name="getUserName"><wsdl:input message="tns:getUserName" name="getUserName"> </wsdl:input><wsdl:output message="tns:getUserNameResponse" name="getUserNameResponse"> </wsdl:output></wsdl:operation><wsdl:operation name="getUser"><wsdl:input message="tns:getUser" name="getUser"> </wsdl:input><wsdl:output message="tns:getUserResponse" name="getUserResponse"> </wsdl:output></wsdl:operation></wsdl:portType><wsdl:binding name="UserServiceSoapBinding" type="tns:UserService"><soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/><wsdl:operation name="getUserList"><soap:operation soapAction="" style="document"/><wsdl:input name="getUserList"><soap:body use="literal"/></wsdl:input><wsdl:output name="getUserListResponse"><soap:body use="literal"/></wsdl:output></wsdl:operation><wsdl:operation name="getUserName"><soap:operation soapAction="" style="document"/><wsdl:input name="getUserName"><soap:body use="literal"/></wsdl:input><wsdl:output name="getUserNameResponse"><soap:body use="literal"/></wsdl:output></wsdl:operation><wsdl:operation name="getUser"><soap:operation soapAction="" style="document"/><wsdl:input name="getUser"><soap:body use="literal"/></wsdl:input><wsdl:output name="getUserResponse"><soap:body use="literal"/></wsdl:output></wsdl:operation></wsdl:binding><wsdl:service name="UserService"><wsdl:port binding="tns:UserServiceSoapBinding" name="UserServiceImplPort"><soap:address location="http://localhost:8080/ws/user"/></wsdl:port></wsdl:service></wsdl:definitions>
3.5.2、根據wsdl生成java程式碼在客戶端pom檔案中新增外掛
<plugin> <groupId>org.apache.cxf</groupId> <artifactId>cxf-codegen-plugin</artifactId> <version>3.4.1</version> <executions> <execution> <id>generate-sources</id> <phase>generate-sources</phase> <configuration> <sourceRoot>${project.build.directory}/generated-sources/cxf</sourceRoot> <wsdlOptions> <wsdlOption> <wsdl>${basedir}/src/main/resources/wsdl/user.wsdl</wsdl> </wsdlOption> </wsdlOptions> </configuration> <goals> <goal>wsdl2java</goal> </goals> </execution> </executions> </plugin>
執行外掛cxf-codegen ==>wsdl2java
執行結束會在target ==> generated-sources中生成對應java檔案,其中org.ouyushan.cxf.ws為指定名稱空間導致生成的包目錄,可將整個包對應複製到客戶端對應包下,當檔案的包路徑發生變化時,UserService中所有className的類路徑都需要調整。
同時對UserService_Service中對應的wsdlLocation都需要改成
wsdlLocation = "classpath:wsdl/user.wsdl",
3.5.3、客戶端配置類及配置檔案
實現客戶端配置類CxfClientConfig
package org.ouyushan.cxf.config;import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;import org.ouyushan.cxf.ws.UserService;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;/** * @Description: * @Author: ouyushan * @Email: [email protected] * @Date: 2020/11/26 16:52 */@Configurationpublic class CxfClientConfig { @Value("${server.serviceUrl}") private String serviceUrl; @Bean public UserService userService() throws Exception { JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean(); jaxWsProxyFactoryBean.setServiceClass(UserService.class); jaxWsProxyFactoryBean.setAddress(serviceUrl); UserService userService = (UserService) jaxWsProxyFactoryBean.create(); return userService; }}
application.yml
server: port: 7070 serviceUrl: http://localhost:8080/ws/user
3.5.4、客戶端服務類
客戶端編寫UserFacadeService
package org.ouyushan.cxf.service;import org.ouyushan.cxf.ws.User;import org.ouyushan.cxf.ws.UserService;import org.springframework.stereotype.Service;import javax.annotation.Resource;import java.util.List;/** * @Description: * @Author: ouyushan * @Email: [email protected] * @Date: 2020/11/26 17:00 */@Servicepublic class UserFacadeService { /** * 資料接入服務 */ @Resource private UserService userService; public String getUsername(String userId) { String userName = userService.getUserName(userId); return userName; } public User getUser(String userId) { User user = userService.getUser(userId); return user; } public List<User> getUserList(String userId) { List<User> userList = userService.getUserList(userId); return userList; }}
3.5.5、客戶端服務類客戶端實現UserController
package org.ouyushan.cxf.controller;import org.ouyushan.cxf.service.UserFacadeService;import org.ouyushan.cxf.ws.User;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;import java.util.List;/** * @Description: * @Author: ouyushan * @Email: [email protected] * @Date: 2020/11/26 17:01 */@RestController@RequestMapping("/user")public class UserController { @Resource private UserFacadeService userFacadeService; @GetMapping("/get") public String getUsername(String userId){ System.out.println("client#userId:" + userId); return userFacadeService.getUsername(userId); } @GetMapping("/getUser") public User getUser(String userId){ System.out.println("client#userId:" + userId); return userFacadeService.getUser(userId); } @GetMapping("/getUserList") public List<User> getUserList(String userId){ System.out.println("client#userId:" + userId); return userFacadeService.getUserList(userId); }}
3.5.6、客戶端驗證啟動客戶端
訪問:
http://localhost:6060/user/get?userId=1
返回:
tom
四、總結4.1、服務端定義cxfServlet
服務端需要定義一個cxfServlet來指定webservice的urlMappings,可以透過配置bean來實現,也可以直接在application中配置
cxf: servlet: /ws/*
/ws/與服務端路徑中的ws對應,
http://localhost:8080/ws/user?wsdl
4.2、服務端定義endpoint
服務端可透過配置endpoint來發布服務,具體服務路徑可透過publish方法來指定,一個服務對應一個wsdl檔案,統一配置類中可釋出多個不同名的服務。
endpoint.publish("/user");
/user與服務端路徑中的user對應,
http://localhost:8080/ws/user?wsdl
4.3、服務端名稱空間服務端userservice介面需要指定名稱空間,並顯示指定入參名稱:
名稱空間:
@WebService(targetNamespace = "http://ws.cxf.ouyushan.org/")
顯示指定入參名稱,指定入參名稱為userId 預設為arg0
@WebParam(name = "userId")
4.4、客戶端配置類需指定服務端url
http://localhost:8080/ws/user
4.5、wsdl生成java程式碼
客戶端生成的類檔案(UserService),若包路徑發生變更時,對應介面檔案中的className也需更新。
至此,spring boot整合cxf實現webservice的請求與服務的簡單例項已完成。但是在除錯過程中您會發現,日誌輸出中沒有關於webservice的相關資訊,怎麼實現能?下一章節將會實現這一問題。