SpringBoot整合WebService


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM