WebService是一個SOA(面向服務的編程)的架構,它是不依賴於語言,不依賴於平台,可以實現不同的語言間的相互調用,通過Internet進行基於Http協議的網絡應用間的交互。 其實WebService並不是什么神秘的東西,它就是一個可以遠程調用的類,或者說是組件,把你本地的功能開放出去共別人調用。具體的說,Web Service可以讓你的網站使用其他網站的資源,比如在網頁上顯示天氣、地圖、twitter上的最新動態等等。
一、為什么用WebService
比如你的項目需要查詢某銀行賬戶余額。你能直接查嗎,肯定不行,因為數據庫是銀行的,他不可能給你權限。你想訪問他的數據庫獲取數據,這就需要用到WebService。通過調用銀行暴露的接口來得到你想要的數據。
1. 適用場景
軟件的集成和復用,如氣象局(服務端系統)、天氣查詢網站等。
- 發布一個服務(對內/對外),不考慮客戶端類型,不考慮性能,建議WebService
- 服務端已經確定使用webservice,客戶端不能選擇,必須使用WebService
軟件集成即通過遠程調用技術,將兩個系統整合到一起,從而實現軟件集成。
軟件復用即同一個款軟件的多次集成,最終實現復用。
2. 不適用場景
- 考慮性能時不建議使用WebService:采用xml格式封裝數據,所以在傳輸過程中,要傳輸額外的標簽,隨着soap協議的不斷完善,標簽越來越大,導致webservice的性能下降。
- 同構程序下不建議使用webservice,比如java 用RMI,不需要翻譯成XML的數據。
二、WebService的原理
在Web Service的體系架構中有三個角色:服務提供者(Service Provider),也叫服務生產者;服務請求者(Service Requester),也叫服務消費者;服務注冊中心(Service Register),也叫服務代理,服務提供者在這里發布服務,服務請求者在這里查找服務,獲取服務的綁定信息。
角色間主要有三個操作:
- 發布(Publish),服務提供者把服務按照規范格式發布到服務注冊中心;
- 查找(Find),服務請求者根據服務注冊中心提供的規范接口發出查找請求,獲取綁定服務所需的相關信息。
- 綁定(Bind),服務請求者根據服務綁定信息對自己的系統進行配置,從而可以調用服務提供者提供的服務。
Web Service的實現是通過SOAP在Web上提供的軟件服務,使用WSDL文件進行說明,並通過UDDI進行注冊。
相關概念:
- XML:(Extensible Markup Language)擴展型可標記語言。面向短期的臨時數據處理、面向萬維網絡,是SOAP的基礎。
- SOAP:(Simple Object Access Protocol)簡單對象存取協議。是XML Web Service 的通信協議。當用戶通過UDDI找到你的WSDL描述文檔后,他通過可以SOAP調用你建立的Web服務中的一個或多個操作。SOAP是XML文檔形式的調用方法的規范,它可以支持不同的底層接口,像HTTP(S)或者SMTP。
- WSDL:(Web Services Description Language) WSDL 文件是一個 XML 文檔,用於說明一組 SOAP 消息以及如何交換這些消息。大多數情況下由軟件自動生成和使用。
- UDDI (Universal Description, Discovery, and Integration) 是一個主要針對Web服務供應商和使用者的新項目。在用戶能夠調用Web服務之前,必須確定這個服務內包含哪些商務方法,找到被調用的接口定義,還要在服務端來編制軟件,UDDI是一種根據描述文檔來引導系統查找相應服務的機制。UDDI利用SOAP消息機制(標准的XML/HTTP)來發布,編輯,瀏覽以及查找注冊信息。它采用XML格式來封裝各種不同類型的數據,並且發送到注冊中心或者由注冊中心來返回需要的數據。
三、Axis2與CXF的區別
目前java開發WebService的框架主要包括Axis2和CXF,如果你需要多語言的支持,你應該選擇Axis2。如果你需要把你的實現側重java並希望和Spring集成,CXF就是更好的選擇,特別是把你的WebService嵌入其他的程序中。
四、SpringBoot使用CXF集成WebService
1. 服務端構建
(1) 引入依賴
<!-- 核心啟動器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- web啟動器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- webService--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web-services</artifactId> </dependency> <!-- CXF webservice --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxws</artifactId> <version>3.2.1</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>3.2.1</version> </dependency> <!-- CXF webservice -->
(2) 接口
@WebService(name = "ServerServiceDemo", targetNamespace = "http://server.webservice.example.com") public interface ServerServiceDemo { @WebMethod String emrService(@WebParam String data); }
(3) 接口實現類
/** * WebService涉及到的有這些 "四解三類 ", 即四個注解,三個類 * @WebMethod * @WebService * @WebResult * @WebParam * SpringBus * Endpoint * EndpointImpl * * 一般我們都會寫一個接口,然后再寫一個實現接口的實現類,但是這不是強制性的 * @WebService 注解表明是一個webservice服務。 * name:對外發布的服務名, 對應於<wsdl:portType name="ServerServiceDemo"></wsdl:portType> * targetNamespace:命名空間,一般是接口的包名倒序, 實現類與接口類的這個配置一定要一致這種錯誤 * Exception in thread "main" org.apache.cxf.common.i18n.UncheckedException: No operation was found with the name xxxx * 對應於targetNamespace="http://server.webservice.example.com" * endpointInterface:服務接口全路徑(如果是沒有接口,直接寫實現類的,該屬性不用配置), 指定做SEI(Service EndPoint Interface)服務端點接口 * serviceName:對應於<wsdl:service name="ServerServiceDemoImplService"></wsdl:service> * portName:對應於<wsdl:port binding="tns:ServerServiceDemoImplServiceSoapBinding" name="ServerServiceDemoPort"></wsdl:port> * * @WebMethod 表示暴露的服務方法, 這里有接口ServerServiceDemo存在,在接口方法已加上@WebMethod, 所以在實現類中不用再加上,否則就要加上 * operationName: 接口的方法名 * action: 沒發現又什么用處 * exclude: 默認是false, 用於阻止將某一繼承方法公開為web服務 * * @WebResult 表示方法的返回值 * name:返回值的名稱 * partName: * targetNamespace: * header: 默認是false, 是否將參數放到頭信息中,用於保護參數,默認在body中 * * @WebParam * name:接口的參數 * partName: * targetNamespace: * header: 默認是false, 是否將參數放到頭信息中,用於保護參數,默認在body中 * model:WebParam.Mode.IN/OUT/INOUT */ @Component @WebService(name = "ServerServiceDemo", targetNamespace = "http://server.webservice.example.com", endpointInterface = "com.example.webservice.service.ServerServiceDemo") public class ServerServiceDemoImpl implements ServerServiceDemo{ @Override public String emrService(@WebParam String data) { if(null == data || "".equals(data.trim())){ return "傳入的參數為空"; } return "調用成功"; } }
(4) 接口發布類
/** * 注意: * org.apache.cxf.Bus * org.apache.cxf.bus.spring.SpringBus * org.apache.cxf.jaxws.EndpointImpl * javax.xml.ws.Endpoint */ @Configuration public class WebServiceConfig { @Autowired private ServerServiceDemo serverServiceDemo; /** * Apache CXF 核心架構是以BUS為核心,整合其他組件。 * Bus是CXF的主干, 為共享資源提供一個可配置的場所,作用類似於Spring的ApplicationContext,這些共享資源包括 * WSDl管理器、綁定工廠等。通過對BUS進行擴展,可以方便地容納自己的資源,或者替換現有的資源。默認Bus實現基於Spring架構, * 通過依賴注入,在運行時將組件串聯起來。BusFactory負責Bus的創建。默認的BusFactory是SpringBusFactory,對應於默認 * 的Bus實現。在構造過程中,SpringBusFactory會搜索META-INF/cxf(包含在 CXF 的jar中)下的所有bean配置文件。 * 根據這些配置文件構建一個ApplicationContext。開發者也可以提供自己的配置文件來定制Bus。 */ @Bean(name = Bus.DEFAULT_BUS_ID) public SpringBus springBus() { return new SpringBus(); } /** * 此方法作用是改變項目中服務名的前綴名,此處127.0.0.1或者localhost不能訪問時,請使用ipconfig查看本機ip來訪問 * 此方法被注釋后, 即不改變前綴名(默認是services), wsdl訪問地址為 http://127.0.0.1:8080/services/ws/api?wsdl * 去掉注釋后wsdl訪問地址為:http://127.0.0.1:8080/soap/ws/api?wsdl * http://127.0.0.1:8080/soap/列出服務列表 或 http://127.0.0.1:8080/soap/ws/api?wsdl 查看實際的服務 * 新建Servlet記得需要在啟動類添加注解:@ServletComponentScan * * 如果啟動時出現錯誤:not loaded because DispatcherServlet Registration found non dispatcher servlet dispatcherServlet * 可能是springboot與cfx版本不兼容。 * 同時在spring boot2.0.6之后的版本與xcf集成,不需要在定義以下方法,直接在application.properties配置文件中添加: * cxf.path=/service(默認是services) */ //@Bean //public ServletRegistrationBean dispatcherServlet() { // return new ServletRegistrationBean(new CXFServlet(), "/soap/*"); //} @Bean public Endpoint endpoint() { EndpointImpl endpoint = new EndpointImpl(springBus(), serverServiceDemo); endpoint.publish("/ws/api"); return endpoint; } }
(5) 啟動項目
訪問地址:http://localhost:8080/services/ 列出在services下的所有服務列表
http://localhost:8080/services/ws/api?wsdl 查看訪問具體的服務信息。
標簽的重要信息說明可以參考:https://blog.csdn.net/xxssyyyyssxx/article/details/50292787
3. 客戶端構建
創建一個新的項目。
(1) 引入依賴
<!-- 核心啟動器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- webService--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web-services</artifactId> </dependency> <!-- CXF webservice --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-spring-boot-starter-jaxws</artifactId> <version>3.2.1</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>3.2.1</version> </dependency> <!-- CXF webservice --> <!-- 如果使用代理類工廠的方式, 因需要知道服務端發布的接口名,所以這里是需要引入服務端的接口模塊。 服務端一般需要將所有對外接口抽取到單獨的一個模塊,再再pom.xml進行引入 -->
(2) 調用代碼
/** * 1.代理類工廠的方式,需要拿到對方的接口地址, 同時需要引入接口 */ // public static void invokeService_1(){ // // 接口地址 // String address = "http://localhost:8080/services/ws/api?wsdl"; // // 代理工廠 // JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean(); // // 設置代理地址 // jaxWsProxyFactoryBean.setAddress(address); // // 設置接口類型 // jaxWsProxyFactoryBean.setServiceClass(ServerServiceDemo.class); // // 創建一個代理接口實現 // ServerServiceDemo us = (ServerServiceDemo) jaxWsProxyFactoryBean.create(); // // 數據准備 // String data = "hello world"; // // 調用代理接口的方法調用並返回結果 // String result = us.emrService(data); // System.out.println("返回結果:" + result); // } /** * 2. 動態調用 */ public static void invokeService_2(){ // 創建動態客戶端 JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance(); Client client = dcf.createClient("http://localhost:8080/services/ws/api?wsdl"); // 需要密碼的情況需要加上用戶名和密碼 // client.getOutInterceptors().add(new ClientLoginInterceptor(USER_NAME, PASS_WORD)); Object[] objects = new Object[0]; try { // invoke("方法名",參數1,參數2,參數3....); //這里注意如果是復雜參數的話,要保證復雜參數可以序列化 objects = client.invoke("emrService", "hello world"); System.out.println("返回數據:" + objects[0]); } catch (java.lang.Exception e) { e.printStackTrace(); } }
參考:
https://blog.csdn.net/sujin_/article/details/83865124
https://www.cnblogs.com/zjfjava/p/9000063.html
https://blog.csdn.net/xxssyyyyssxx/article/details/50292787