最近有個單位內網系統須要對接統一門戶,進行單點登陸和待辦事項對接功能。通常上政府系統都會要求作統一登陸功能,這個沒啥問題,反正業務系統都是作單點登陸的,改下
shiro
相關類就行了。看了接入方案,作坑爹的是須要業務系統提供一個webService
服務,供統一平臺調用。對於ws
服務,是真的除了大學期間要去寫個調用天氣預報的做業後,就再也沒有接觸過了。查閱了SpringBoot
文檔後,發現確實有一章節是將webService
的,因此,今天就來簡單介紹下Spring Web Service
的集成和使用吧。前端
Web Service
技術,能使得運行在不一樣機器上的不一樣應用無須藉助附加的、專門的第三方軟件或硬件, 就可相互交換數據或集成。依據Web Service
規範實施的應用之間,不管它們所使用的語言、平臺或內部協議是什麼,均可以相互交換數據。java
簡單的說,WebService就是一種跨編程語言和跨操做系統平臺的遠程調用技術。git
如下內容摘自百度百科:Web Service程序員
Web Service
平臺須要一套協議來實現分佈式應用程序的建立。任何平臺都有它的數據表示方法和類型系統。要實現互操做性,Web Service
平臺必須提供一套標準的類型系統,用於溝通不一樣平臺、編程語言和組件模型中的不一樣類型系統。這些協議有:github
可擴展的標記語言是Web Service
平臺中表示數據的基本格式。除了易於創建和易於分析外,XML
主要的優勢在於它既與平臺無關,又與廠商無關。XML
是由萬維網協會(W3C)建立,W3C制定的XML SchemaXSD定義了一套標準的數據類型,並給出了一種語言來擴展這套數據類型。 Web Service
平臺是用XSD
來做爲數據類型系統的。當你用某種語言如VB. NET
或C#
來構造一個Web Service時,爲了符合Web Service
標準,全部你使用的數據類型都必須被轉換爲XSD
類型。如想讓它使用在不一樣平臺和不一樣軟件的不一樣組織間傳遞,還須要用某種東西將它包裝起來。這種東西就是一種協議,如 SOAP
。web
XSD
全稱爲XML Schemas Definition
,即:XML結構定義。是描述xml的,同時遵循xml規範。spring
SOAP
即簡單對象訪問協議(Simple Object Access Protocol),它是用於交換XML(標準通用標記語言下的一個子集)編碼信息的輕量級協議。它有三個主要方面:XML-envelope爲描述信息內容和如何處理內容定義了框架,將程序對象編碼成爲XML對象的規則,執行遠程過程調用(RPC)的約定。SOAP能夠運行在任何其餘傳輸協議上。例如,你可使用 SMTP,即因特網電子郵件協議來傳遞SOAP消息,這但是頗有誘惑力的。在傳輸層之間的頭是不一樣的,但XML有效負載保持相同。 Web Service 但願實現不一樣的系統之間可以用「軟件-軟件對話」的方式相互調用,打破了軟件應用、網站和各類設備之間的格格不入的狀態,實現「基於Web無縫集成」的目標。apache
Web Service
描述語言WSDL
就是用機器能閱讀的方式提供的一個正式描述文檔而基於XML
的語言,用於描述Web Service及其函數、參數和返回值。由於是基於XML的,因此WSDL既是機器可閱讀的,又是人可閱讀的。編程
UDDI
的目的是爲電子商務創建標準;UDDI
是一套基於Web的、分佈式的、爲Web Service
提供的、信息註冊中心的實現標準規範,同時也包含一組使企業能將自身提供的Web Service
註冊,以使別的企業可以發現的訪問協議的實現標準。後端
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-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
直接調用。
建立一個新工程: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
來訪問下服務。
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
以上只是基於官方文檔,簡單的示例了一遍,具體一些高級用法以及相關安全校驗、過濾器等等,沒有過多涉及的。以後有時間再填坑吧,畢竟這個用的真的很少呀。
本章節主要簡單介紹了
spring-ws
的使用。本來是沒有打算寫關於WebService
相關的。只是機緣巧合下恰好有個對接系統須要用上,就臨時嘗試一下了。還有不少深刻的功能,就沒有過多涉及了。等到時候真正開始對接時,有碰到一些問題或者有些知識點補充的,再來補充吧。畢竟,我想如今除了舊系統和政府部門的系統,應該不多再去開發webservice
服務了吧。官網文檔大體看了下,也確實以爲有點複雜呀,不知道是否是理解能力問題,⊙﹏⊙‖∣。理論上,按着規則走,問題應該也不是很大。就是一些好比無參數如何調用,或者返回參數節點自定義問題,這些理論上均可以使用提供的攔截器來完成的。有問題,仍是建議查看官網吧,真的比較詳細。最後看了cxf
,也比較簡單。下一篇就來寫寫使用cxf
來發布webservice
,多嘗試幾種方式~
目前互聯網上不少大佬都有
SpringBoot
系列教程,若有雷同,請多多包涵了。原創不易,碼字不易,還但願你們多多支持。若文中有所錯誤之處,還望提出,謝謝。
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/