SpringBoot | 第三十三章:Spring web Servcies整合和使用
前言
最近有個單位內網系統需要對接統一門戶,進行單點登入和待辦事項對接功能。一般上政府系統都會要求做統一登入功能,這個沒啥問題,反正業務系統都是做單點登入的,改下
shiro
相關類就好了。看了接入方案,做坑爹的是需要業務系統提供一個webService
服務,供統一平臺呼叫。對於ws
服務,是真的除了大學期間要去寫個呼叫天氣預報的作業後,就再也沒有接觸過了。查閱了SpringBoot
文件後,發現確實有一章節是將webService
的,所以,今天就來簡單介紹下Spring Web Service
的整合和使用吧。
- 一點知識
- 何為WebService
- WebServcie技術支援
- XML和XSD
- SOAP
- WSDL
- UDDI
- 何為Spring-Web-Services
- Spring-WS服務端釋出
- Spirng-WS客戶端呼叫
- 簡單使用CXF呼叫webService
- 有待補充
- 參考資料
- 總結
- 最後
- 老生常談
一點知識
何為WebService
Web Service
技術,能使得執行在不同機器上的不同應用無須藉助附加的、專門的第三方軟體或硬體, 就可相互交換資料或整合。依據Web Service
規範實施的應用之間,無論它們所使用的語言、平臺或內部協議是什麼,都可以相互交換資料。
簡單的說,WebService就是一種跨程式語言和跨作業系統平臺的遠端呼叫技術。
WebServcie技術支援
以下內容摘自百度百科:Web Service
Web Service
平臺需要一套協議來實現分散式應用程式的建立。任何平臺都有它的資料表示方法和型別系統。要實現互操作性,Web Service
平臺必須提供一套標準的型別系統,用於溝通不同平臺、程式語言和元件模型中的不同型別系統。這些協議有:
XML和XSD
可擴充套件的標記語言是Web Service
平臺中表示資料的基本格式。除了易於建立和易於分析外,XML
主要的優點在於它既與平臺無關,又與廠商無關。XML
是由全球資訊網協會(W3C)建立,W3C制定的XML SchemaXSD定義了一套標準的資料型別,並給出了一種語言來擴充套件這套資料型別。Web Service
XSD
來作為資料型別系統的。當你用某種語言如VB. NET
或C#
來構造一個Web Service時,為了符合Web Service
標準,所有你使用的資料型別都必須被轉換為XSD
型別。如想讓它使用在不同平臺和不同軟體的不同組織間傳遞,還需要用某種東西將它包裝起來。這種東西就是一種協議,如 SOAP
。
XSD
全稱為XML Schemas Definition
,即:XML結構定義。是描述xml的,同時遵循xml規範。
SOAP
SOAP
即簡單物件訪問協議(Simple Object Access Protocol),它是用於交換XML(標準通用標記語言下的一個子集)編碼資訊的輕量級協議。它有三個主要方面:XML-envelope為描述資訊內容和如何處理內容定義了框架,將程式物件編碼成為XML物件的規則,執行遠端過程呼叫(RPC)的約定。SOAP可以執行在任何其他傳輸協議上。例如,你可以使用 SMTP,即因特網電子郵件協議來傳遞SOAP訊息,這可是很有誘惑力的。在傳輸層之間的頭是不同的,但XML有效負載保持相同。
Web Service 希望實現不同的系統之間能夠用“軟體-軟體對話”的方式相互呼叫,打破了軟體應用、網站和各種裝置之間的格格不入的狀態,實現“基於Web無縫整合”的目標。
WSDL
Web Service
描述語言WSDL
就是用機器能閱讀的方式提供的一個正式描述文件而基於XML
的語言,用於描述Web Service及其函式、引數和返回值。因為是基於XML的,所以WSDL既是機器可閱讀的,又是人可閱讀的。
UDDI
UDDI
的目的是為電子商務建立標準;UDDI
是一套基於Web的、分散式的、為Web Service
提供的、資訊註冊中心的實現標準規範,同時也包含一組使企業能將自身提供的Web Service
註冊,以使別的企業能夠發現的訪問協議的實現標準。
何為Spring-Web-Services
Spring Web services
是Spring
推出的一款構建webservice
服務的框架。其主要側重點是建立文件驅動的Web服務。Spring Web Services
專案促進了契約優先的SOAP服務開發,提供了多種方式來建立靈活的Web服務,這些服務可以通過多種方式處理XML負載。可無縫地使用Spring依賴注入和配置等概念。
Spring-WS
專案由由以下幾個專案組成:
Spring-WS Core(
spring-ws-core.jar
) - 它是主要模組,提供WebServiceMessage和SoapMessage等中央介面,伺服器端框架,強大的訊息分發功能和支援類來實現Web服務端點。 它還提供Web Service消費者客戶端作為:WebServiceTemplate。Spring-WS Support(
spring-ws-support.jar
) − 該模組為JMS,電子郵件等提供支援。Spring-WS Security(
spring-ws-security.jar
) - 該模組負責提供與核心Web服務模組整合的WS-Security實現。 使用這個模組,可以新增主體令牌,簽名,加密和解密SOAP訊息。該模組允許使用現有的Spring Security實現進行認證和授權。Spring XML(
spring-xml.jar
) − 該模組為Spring Web Services提供XML支援類。 該模組由Spring-WS框架內部使用。Spring OXM - 該模組提供了XML與物件對映的支援類。
之間的依賴關係,如下圖所示:
簡單來說,看了官網文件後,一切遵循契約優先原則,請求和響應的引數都應遵循約定,不然wsdl
檔案生成是錯誤的,這裡踩了坑。。
Spring-WS服務端釋出
spring-ws
像spring-mvc
一樣,在整合到web專案時,前端有個servlet分發請求訊息的概念。
這個servlet接受soap訊息,通過對映轉發到後端的服務實現類方法中(Endpiont)
在請求進來處理過程中,可以新增,攔截器(Interceptor),異常處理器(ExceptionResolver)。
通過攔截器可以做一些額外的定製功能,比如安全。通過異常處理器定製異常資訊顯示,處理等。
這個servlet就是MessageDispatcher
,來看看官網給出的處理流程圖:
所以在需要對請求引數或者響應引數做處理時,可以編寫對應的攔截器進行處理的。
現在,以一個簡單示例來發佈一個webService
服務。建立工程:spring-boot-webservice-server
。
0.引入POM依賴。
<!-- spirng ws 依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<!-- 生成wsdl檔案 -->
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
</dependency>
1.建立一個xsd
檔案,用來描述請求和響應的各實體資訊。這裡簡單以一個獲取作者資訊為例子。author.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.lqdev.cn/webservice"
targetNamespace="http://www.lqdev.cn/webservice" elementFormDefault="qualified">
<!-- 定義請求實體 -->
<xs:element name="authorRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- 定義響應實體 -->
<xs:element name="authorResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="author" type="tns:author"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- 定義請求實體 -->
<xs:element name="authorListRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="nonce" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- 定義響應實體 -->
<xs:element name="authorListResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="author" type="tns:author" minOccurs="1" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- 定義作者 資訊 -->
<xs:complexType name="author">
<xs:sequence>
<xs:element name="name" type="xs:string" />
<!-- 愛好 列表形式 nillable=true 可為空 ,maxOccurs=unbouned 無限 -->
<xs:element name="hobby" type="xs:string" nillable="true" maxOccurs="unbounded" />
<!-- 性別 列舉型別 限定 -->
<xs:element name="sex" type="tns:sex" />
<!-- 生日 -->
<xs:element name="birthday" type="xs:string" />
<!-- 描述 -->
<xs:element name="description" type="xs:string" />
</xs:sequence>
</xs:complexType>
<!-- 列舉型別 性別:男 女 -->
<xs:simpleType name="sex">
<xs:restriction base="xs:string">
<xs:enumeration value="male"/>
<xs:enumeration value="female"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
這裡需要注意,請求和返回的名字是有要求的,兩個名字前面要一樣,字尾分別是固定的配置,預設為Request和Response; 當然可以通過requestSuffix
和responseSuffix
屬性來修改預設值的,在配置小節會說到。
關於xsd
規則,可以檢視:http://www.w3school.com.cn/schema/index.asp。
2.根據XSD檔案建立實體物件。這裡直接使用maven
建立自動生成。pom中加入外掛:jaxb2-maven-plugin
。
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>xjc</id>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<schemaDirectory>${project.basedir}/src/main/resources/</schemaDirectory>
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
<clearOutputDir>false</clearOutputDir>
<!-- 包名路徑 --> <packageName>cn.lqdev.learning.springboot.ws.webservice</packageName>
</configuration>
</plugin>
然後執行下命令:mvn install
就會自動建立了。正常情況下,新增後,xsd
檔案有變動,都會實時建立對應實體物件的。此時,生成的物件如下:
3.建立Endpoint服務,有點類似Controller
,請求服務的入口。
/**
* 建立endpoint 類似於建立controller了。
* @author oKong
*
*/
@Endpoint
public class AuthorEndpoint {
@PayloadRoot(namespace = WsConst.NAMESPACE_URI, localPart = "authorRequest")
@ResponsePayload
public AuthorResponse getAuthor(@RequestPayload AuthorRequest authorReq){
AuthorResponse resp = new AuthorResponse();
Author author = new Author();
author.setBirthday("1990-01-23");
author.setName("姓名:" + authorReq.getName());
author.setSex(Sex.FEMALE);
author.getHobby().addAll(Arrays.asList("電影","旅遊"));
author.setDescription("描述:一枚趔趄的猿。現在時間:" + new Date().getTime());
resp.setAuthor(author);
return resp;
}
@PayloadRoot(namespace = WsConst.NAMESPACE_URI, localPart = "authorListRequest")
@ResponsePayload
public AuthorListResponse getAuthorList(@RequestPayload AuthorListRequest request){
AuthorListResponse resp = new AuthorListResponse();
Author author = new Author();
author.setBirthday("1990-01-23");
author.setName("姓名:oKong");
author.setSex(Sex.FEMALE);
author.getHobby().addAll(Arrays.asList("電影","旅遊"));
author.setDescription("描述:一枚趔趄的猿。現在時間:" + new Date().getTime());
resp.getAuthor().add(author);
resp.getAuthor().add(author);
return resp;
}
}
示例程式碼,只是為了演示,大部分資訊都固定寫死了。實際開發中,可以加入各自的業務邏輯,引入相應的service
類的。
而且,這裡需要注意:
- 方法宣告上的
@PayloadRoot
標註中的namespace
和localPart
分別就是wsdl中的targetNamespace
和soap
方法名稱。 @ResponsePayload
和@RequestPayload
這兩個標註的用法,以及它們對應的資料型別就是此前通過maven外掛對wsdl定義生成的java類。
關於請求引數的型別,是否需要加@RequestPayload
說明:
一般上,都是使用JAXB2
物件了,也就是先前生成的實體物件。當然,有興趣的同學可以試試,其他的物件引數,可以獲取到不同的引數值的。
比如:
public void handle(@RequestPayload Element element)
一個org.w3c.dom.Element
物件。
public void handle(@RequestPayload DOMSource domSource, SoapHeader header)
這樣,能獲取到SOAP
的頭部資訊。
其他相關用法,可以檢視此地址:https://docs.spring.io/spring-ws/docs/2.4.2.RELEASE/reference/#server-atEndpoint-methods
關於響應的引數,是否需要加@ResponsePayload
,一下是官網給出的說明資訊:
這個有個坑:嘗試無引數請求時,使用postman傳送xml資料可以正常請求,但使用spirng-ws
呼叫就呼叫不到了,嗯,我想應該是我呼叫方法不多。。⊙﹏⊙‖∣
4.建立配置類,生效webservice服務。
/**
* ws-配置
* @author oKong
*
*/
@EnableWs //開啟webService
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter{
@Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);//true 地址會進行轉換,不然都是本地地址
//這裡可以設定 請求的工廠類,實現有兩個:SaajSoapMessageFactory 和 AxiomSoapMessageFactory
//預設是 SaajSoapMessageFactory
// servlet.setMessageFactoryBeanName(messageFactoryBeanName);
return new ServletRegistrationBean(servlet, "/ws/*");
}
//name 就是對應 wsdl名如 :/ws/author.wsdl
@Bean(name = "author")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema authorSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("AuthorPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setSchema(authorSchema);
wsdl11Definition.setTargetNamespace(WsConst.NAMESPACE_URI);
return wsdl11Definition;
}
//可自定義SaajSoapMessageFactory 然後指定其SOAP版本
@Bean
public SaajSoapMessageFactory messageFactory() {
SaajSoapMessageFactory messageFactory = new SaajSoapMessageFactory();
//指定版本
messageFactory.setSoapVersion(SoapVersion.SOAP_11);//SoapVersion.SOAP_12
return messageFactory;
}
@Bean
public XsdSchema authorSchema() {
return new SimpleXsdSchema(new ClassPathResource("author.xsd"));
}
@Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
//可以自定義攔截器
}
}
常量類:WsConst.java
/**
* 常量類
* @author oKong
*
*/
public class WsConst {
public static final String NAMESPACE_URI = "http://www.lqdev.cn/webservice";
}
5.編寫啟動類。
/**
* web-service 簡單示例
*
* @author oKong
*
*/
@SpringBootApplication
@Slf4j
public class WebServiceApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(WebServiceApplication.class, args);
log.info("spring-boot-webservice-server-chapter33啟動!");
}
}
6.啟動應用,訪問下:http://127.0.0.1:8090/ws/author.wsdl ,可以看見wsdl檔案內容了。
接著,我們使用postman
呼叫下:POST http://127.0.0.1:8090/ws
說明已經正常釋出了。接下來,我們使用spring-ws
直接呼叫。
Spirng-WS客戶端呼叫
建立一個新工程:spring-boot-webservice-client
0.引入POM依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
</dependency>
1.獲取wsdl檔案,放入src\main\resources\schemas\
資料夾中,同時加入maven
外掛:maven-jaxb2-plugin
使其生成對應實體列。
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.13.3</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<schemaLanguage>WSDL</schemaLanguage>
<generatePackage>cn.lqdev.webservice</generatePackage>
<generateDirectory>${basedir}/src/main/java</generateDirectory>
<schemas>
<schema>
<fileset>
<!-- Defaults to schemaDirectory. -->
<directory>${basedir}/src/main/resources/schemas</directory>
<!-- Defaults to schemaIncludes. -->
<includes>
<include>*.wsdl</include>
</includes>
</fileset>
</schema>
</schemas>
</configuration>
</plugin>
wsdl檔案就不貼了。目錄為:
生成後對應實體類為:
注意:寫此文章前,嘗試過使用cxf
進行呼叫,而呼叫過程中,發現請求的實體需要在包cn.lqdev.webservice
路徑下,不然校驗不通過。所以為了相容,我直接寫成此路徑了。對於spring ws
而言,包名可以自定義的。不知道cxf
是不是可以修改,跟蹤了下原始碼也沒有找到具體這個規則是怎麼來的,不知道是不是和targetNamespace
的值http://www.lqdev.cn/webservice
有關,有待測試。
2.建立客戶端呼叫類。
/**
* 編寫客戶端 繼承WebServiceGatewaySupport 類 方便呼叫
* @author oKong
*
*/
public class WsAuthorClient extends WebServiceGatewaySupport{
/**
* 獲取作者資訊
* @author oKong
*/
public AuthorResponse getAuthor(String name) {
AuthorRequest req = new AuthorRequest();
req.setName(name);
//使用 marshalSendAndReceive 進行呼叫
return (AuthorResponse) getWebServiceTemplate().marshalSendAndReceive(req);
}
/**
* 獲取作者列表資訊
* @author oKong
*/
public AuthorListResponse getAuthorList(){
AuthorListRequest request = new AuthorListRequest();
request.setNonce(UUID.randomUUID().toString());
return (AuthorListResponse) getWebServiceTemplate().marshalSendAndReceive(request);
}
}
此類,就是呼叫webservice服務的。
4.建立配置類.
/**
* 客戶端呼叫配置
* @author oKong
*
*/
@Configuration
public class WsClientConfig {
@Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
//會掃描此類下面的對應的 jaxb2實體類 因為是使用 Marshaller和 unmarshaller來進行xml和bean直接轉換的
//具體是判斷此路徑下是否包含 ObjectFactory.class 檔案
//設定 JAXBContext 物件
marshaller.setContextPath("cn.lqdev.webservice");
//或者使用 以下方式設定
// marshaller.setPackagesToScan(packagesToScan);
// marshaller.setClassesToBeBound(classesToBeBound);
return marshaller;
}
/*
* 建立bean
*/
@Bean
public WsAuthorClient wsClient(Jaxb2Marshaller marshaller) {
WsAuthorClient client = new WsAuthorClient();
//預設對應的ws服務地址 client請求中還能動態修改的
client.setDefaultUri("http://127.0.0.1:8090/ws");
client.setMarshaller(marshaller);//指定轉換類
client.setUnmarshaller(marshaller);
return client;
}
}
關於marshaller
和 unmarshaller
解析xml和讀取xml相關知識,沒有過多瞭解,感興趣的可以自行搜尋相關資料下。
5.建立示例控制層,呼叫各服務介面。
/**
* 簡單呼叫示例
* @author oKong
*
*/
@RestController
@RequestMapping("/author")
public class DemoController {
@Autowired
WsAuthorClient authorClient;
@GetMapping("/get")
public AuthorResponse getAuthor(String name) {
return authorClient.getAuthor(name);
}
@GetMapping("/list")
public AuthorListResponse getAuthorList() {
return authorClient.getAuthorList();
}
}
6.修改埠號為:8096,同時啟動應用。
server.port=8096
使用Postman
,訪問:http://127.0.0.1:8096/author/get?name=程式設計師
正常情況下可以看見以下返回內容:
說明呼叫成功了。
訪問下列表:http://127.0.0.1:8096/author/list
簡單使用CXF呼叫webService
接下類,嘗試下使用cxf
來訪問下服務。
0.引入cxf
相關依賴。
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.1.11</version>
</dependency>
1.controller類新增一個方法,使用cxf方式呼叫服務。
@GetMapping("/cxf/{method}")
public Object cxf(@PathVariable String method,String name) throws Exception{
//獲取客戶端工廠類
JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
//建立client物件
Client client = dcf.createClient("http://127.0.0.1:8090/ws/author.wsdl");
AuthorListRequest listReq = new AuthorListRequest();
listReq.setNonce(UUID.randomUUID().toString());
AuthorRequest req = new AuthorRequest();
req.setName(name);
//呼叫 第一個方法是operation 值,即呼叫方法,
//其後是呼叫引數。
Object[] objects = new Object[0];
//相關 operation值 可以根據 client.getEndpoint().getBinding().getBindingInfo().getOperations(); 獲取
//有興趣可以看下 client.getEndpoint().getBinding().getBindingInfo()提供的一些方法。
//這裡就簡單的判斷了
if("authorList".equalsIgnoreCase(method)) {
objects = client.invoke("authorList", listReq);
} else {
objects = client.invoke("author", req);
}
//返回的物件objects[0]即為返回的值
return objects[0];
}
這裡需要注意:對應的實體類,需要符合wsdl檔案中解析出來的type Classes 對應上,本例子為:cn.lqdev.webservice.AuthorListRequest
,猜猜應該和targetNamespace
值有關,不然會出現以下類似提示:
Part {http://www.lqdev.cn/webservice}authorListRequest should be of type cn.lqdev.webservice.AuthorListRequest, not cn.lqdev.learning.webservice.AuthorListRequest
最後發現使用cxf
也很簡單呀,下次試試。是利用JAX-WS
規範的。
2.重啟應用,訪問下:http://127.0.0.1:8096/author/cxf/author?name=趔趄的猿 最後效果是一樣的。
訪問:http://127.0.0.1:8096/author/cxf/authorList
有待補充
以上只是基於官方文件,簡單的示例了一遍,具體一些高階用法以及相關安全校驗、過濾器等等,沒有過多涉及的。之後有時間再填坑吧,畢竟這個用的真的不多呀。
參考資料
https://docs.spring.io/spring-ws/docs/2.4.2.RELEASE/reference/
https://spring.io/guides/gs/producing-web-service/
https://spring.io/guides/gs/consuming-web-service/
總結
本章節主要簡單介紹了
spring-ws
的使用。原本是沒有打算寫關於WebService
相關的。只是機緣巧合下剛好有個對接系統需要用上,就臨時嘗試一下了。還有很多深入的功能,就沒有過多涉及了。等到時候真正開始對接時,有碰到一些問題或者有些知識點補充的,再來補充吧。畢竟,我想現在除了舊系統和政府部門的系統,應該很少再去開發webservice
服務了吧。官網文件大致看了下,也確實覺得有點複雜呀,不知道是不是理解能力問題,⊙﹏⊙‖∣。理論上,按著規則走,問題應該也不是很大。就是一些比如無引數如何呼叫,或者返回引數節點自定義問題,這些理論上都可以使用提供的攔截器來完成的。有問題,還是建議檢視官網咖,真的比較詳細。最後看了cxf
,也比較簡單。下一篇就來寫寫使用cxf
來發布webservice
,多嘗試幾種方式~
最後
目前網際網路上很多大佬都有
SpringBoot
系列教程,如有雷同,請多多包涵了。原創不易,碼字不易,還希望大家多多支援。若文中有所錯誤之處,還望提出,謝謝。
老生常談
- 個人QQ:
499452441
- 微信公眾號:
lqdevOps
個人部落格:http://blog.lqdev.cn
完整示例:https://github.com/xie19900123/spring-boot-learning/tree/master/chapter-33
原文地址:http://blog.lqdev.cn/2018/11/09/springboot/chapter-thirty-three/