聊聊Dubbo(二):簡單入門

猿碼道發表於2018-01-22

0 準備

  1. 安裝註冊中心:Zookeeper、Dubbox自帶的dubbo-registry-simple;
  2. 安裝DubboKeeper監控:https://github.com/dubboclub/dubbokeeper;

以上兩點準備,不是本文重點,不做詳細介紹,安裝比較簡單,自行查閱相關資料安裝學習。

1 服務端

1.2 介面定義

  1. 建立Maven模組:msa-demo-api

    msa-demo-api

  2. msa-demo-api:配置pom.xml

    <!-- Dubbox依賴 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>dubbo</artifactId>
        <version>2.8.4</version>
    </dependency>
    <!-- END -->
    
    <!-- 如果要使用lombok -->
    <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
    </dependency>
    <!-- END -->
    
    <!-- 如果要使用REST風格遠端呼叫 -->
    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-jaxrs</artifactId>
        <version>3.0.7.Final</version>
    </dependency>
    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-client</artifactId>
        <version>3.0.7.Final</version>
    </dependency>
    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>1.0.0.GA</version>
    </dependency>
    <!-- END -->
    
    <!-- 如果要使用json序列化 -->
    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-jackson-provider</artifactId>
        <version>3.0.7.Final</version>
    </dependency>
    <!-- END -->
    
    <!-- 如果要使用xml序列化 -->
    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-jaxb-provider</artifactId>
        <version>3.0.7.Final</version>
    </dependency>
    <!-- END -->
    
    <!-- 如果要使用netty server -->
    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-netty</artifactId>
        <version>3.0.7.Final</version>
    </dependency>
    <!-- END -->
    
    <!-- 如果要使用Sun HTTP server -->
    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-jdk-http</artifactId>
        <version>3.0.7.Final</version>
    </dependency>
    <!-- END -->
    
    <!-- 如果要使用tomcat server -->
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-core</artifactId>
        <version>8.0.11</version>
    </dependency>
    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-logging-juli</artifactId>
        <version>8.0.11</version>
    </dependency>
    <!-- END -->
    
    <!-- 如果要使用Kyro序列化 -->
    <dependency>
        <groupId>com.esotericsoftware.kryo</groupId>
        <artifactId>kryo</artifactId>
        <version>2.24.0</version>
    </dependency>
    <dependency>
        <groupId>de.javakaffee</groupId>
        <artifactId>kryo-serializers</artifactId>
        <version>0.26</version>
    </dependency>
    <!-- END -->
    
    <!-- 如果要使用FST序列化 -->
    <dependency>
        <groupId>de.ruedigermoeller</groupId>
        <artifactId>fst</artifactId>
        <version>1.55</version>
    </dependency>
    <!-- END -->
    
    <!-- 如果要使用Jackson序列化 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.3.3</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.3.3</version>
    </dependency>
    <!-- END -->
    複製程式碼

    以上POM配置,從dubbox-2.8.4開始,所有依賴庫的使用方式將和dubbo原來的一樣:即如果要使用REST、Kyro、FST、Jackson等功能,需要使用者自行手工新增相關的依賴。

  3. 定義介面:UserService.java

    /**
     * @author TaoBangren
     * @version 1.0
     * @since 2017/5/17 上午9:26
     */
    public interface UserService {
        User getUser(Long id);
        Long registerUser(User user);
    }
    複製程式碼
  4. 定義REST介面:AnotherUserRestService.java

    package com.alibaba.dubbo.demo.user.facade;
    import com.alibaba.dubbo.demo.user.User;
    import com.alibaba.dubbo.rpc.protocol.rest.support.ContentType;
    import javax.validation.constraints.Min;
    import javax.ws.rs.*;
    import javax.ws.rs.core.MediaType;
    /**
     * @author TaoBangren
     * @version 1.0
     * @since 2017/5/17 上午9:26
     */
    // 在Dubbo中開發REST服務主要都是通過JAX-RS的annotation來完成配置的,
    // 在上面的示例中,我們都是將annotation放在服務的實現類中。但其實,我
    // 們完全也可以將annotation放到服務的介面上,這兩種方式是完全等價的.
    //
    // 在一般應用中,我們建議將annotation放到服務實現類,這樣annotation和
    // java實現程式碼位置更接近,更便於開發和維護。另外更重要的是,我們一般傾向
    // 於避免對介面的汙染,保持介面的純淨性和廣泛適用性。
    // 但是,如後文所述,如果我們要用dubbo直接開發的消費端來訪問此服務,則annotation必須放到介面上。
    // 如果介面和實現類都同時新增了annotation,則實現類的annotation配置會生效,介面上的annotation被直接忽略。
    @Path("u")
    @Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
    @Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8})
    public interface AnotherUserRestService {
    
          @GET
          @Path("{id : \\d+}")
          // 在一個REST服務同時對多種資料格式支援的情況下,根據JAX-RS標準,
          // 一般是通過HTTP中的MIME header(content-type和accept)來指定當前想用的是哪種格式的資料。
          // @Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8})
          // 但是在dubbo中,我們還自動支援目前業界普遍使用的方式,即用一個URL字尾(.json和.xml)來指定
          // 想用的資料格式。例如,在新增上述annotation後,直接訪問http://localhost:8888/users/1001.json
          // 則表示用json格式,直接訪問http://localhost:8888/users/1002.xml則表示用xml格式,
          // 比用HTTP Header更簡單直觀。Twitter、微博等的REST API都是採用這種方式。
          // 如果你既不加HTTP header,也不加字尾,則dubbo的REST會優先啟用在以上annotation定義中排位最靠前的那種資料格式。
          // 注意:這裡要支援XML格式資料,在annotation中既可以用MediaType.TEXT_XML,也可以用MediaType.APPLICATION_XML,
          // 但是TEXT_XML是更常用的,並且如果要利用上述的URL字尾方式來指定資料格式,只能配置為TEXT_XML才能生效。
          User getUser(@PathParam("id") @Min(1L) Long id);
    
          @POST
          @Path("register")
          RegistrationResult registerUser(User user);
    }
    複製程式碼
  5. 定義實體:User.java

    package com.alibaba.dubbo.demo.user;
    import lombok.Data;
    import org.codehaus.jackson.annotate.JsonProperty;
    import javax.validation.constraints.Min;
    import javax.validation.constraints.NotNull;
    import javax.validation.constraints.Size;
    import javax.xml.bind.annotation.XmlAccessType;
    import javax.xml.bind.annotation.XmlAccessorType;
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    import java.io.Serializable;
    /**
     * @author TaoBangren
     * @version 1.0
     * @since 2017/5/17 上午9:26
     */
    // 由於JAX-RS的實現一般都用標準的JAXB(Java API for XML Binding)來序列化和反序列化XML格式資料,
    // 所以我們需要為每一個要用XML傳輸的物件新增一個類級別的JAXB annotation(@XmlRootElement) ,否則序列化將報錯。
    @Data
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    public class User implements Serializable {
    
          @NotNull
          @Min(1L)
          private Long id;
    
          // REST的底層實現會在service的物件和JSON/XML資料格式之間自動做序列化/反序列化。
          // 但有些場景下,如果覺得這種自動轉換不滿足要求,可以對其做定製。
          // Dubbo中的REST實現是用JAXB做XML序列化,用Jackson做JSON序列化,
          // 所以在物件上新增JAXB或Jackson的annotation即可以定製對映。
          @JsonProperty("username")
          @XmlElement(name = "username")
          @NotNull
          @Size(min = 6, max = 50)
          private String name;
    }
    複製程式碼
  6. 定義REST響應結果實體:RegistrationResult.java

    package com.alibaba.dubbo.demo.user.facade;
    import lombok.Data;
    import javax.xml.bind.annotation.XmlRootElement;
    import java.io.Serializable;
    /**
     * @author TaoBangren
     * @version 1.0
     * @since 2017/5/17 上午9:26
     */
    // 此外,如果service方法中的返回值是Java的 primitive型別(如int,long,float,double等),
    // 最好為它們新增一層wrapper物件,因為JAXB不能直接序列化primitive型別。這樣不但能夠解決XML序列化的問題,
    // 而且使得返回的資料都符合XML和JSON的規範。
    // 這種wrapper物件其實利用所謂Data Transfer Object(DTO)模式,採用DTO還能對傳輸資料做更多有用的定製。
    @Data
    @XmlRootElement
    public class RegistrationResult implements Serializable {
          private Long id;
    }
    複製程式碼

1.3 服務實現

  1. 建立Maven模組:msa-demo-provider

    msa-demo-provider

  2. msa-demo-provider:配置pom.xml

    <!-- Module依賴 START -->
    <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>msa-demo-api</artifactId>
         <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- Module依賴 END -->
    複製程式碼
  3. 實現UserService介面:UserServiceImpl.java

    package com.alibaba.dubbo.demo.user;
    import lombok.extern.slf4j.Slf4j;
    import java.util.concurrent.atomic.AtomicLong;
    /**
     * @author TaoBangren
     * @version 1.0
     * @since 2017/5/17 上午9:26
     */
    @Slf4j
    public class UserServiceImpl implements UserService {
          private final AtomicLong idGen = new AtomicLong();
          public User getUser(Long id) {
              User user = new User();
              user.setId(id);
              user.setName("username" + id);
              return user;
          }
      
          public Long registerUser(User user) {
              // System.out.println("Username is " + user.getName());
              return idGen.incrementAndGet();
          }
    }
    複製程式碼
  4. 實現REST介面AnotherUserRestService:AnotherUserRestServiceImpl.java

    package com.alibaba.dubbo.demo.user.facade;
    import com.alibaba.dubbo.demo.user.User;
    import com.alibaba.dubbo.demo.user.UserService;
    import com.alibaba.dubbo.rpc.RpcContext;
    import lombok.extern.slf4j.Slf4j;
    /**
     * @author TaoBangren
     * @version 1.0
     * @since 2017/5/17 上午9:26
     */
    @Slf4j
    public class AnotherUserRestServiceImpl implements AnotherUserRestService {
    
          private UserService userService;
    
          public void setUserService(UserService userService) {
              this.userService = userService;
          }
    
          public User getUser(Long id) {
              System.out.println("Client name is " + RpcContext.getContext().getAttachment("clientName"));
              System.out.println("Client impl is " + RpcContext.getContext().getAttachment("clientImpl"));
              return userService.getUser(id);
          }
    
          public RegistrationResult registerUser(User user) {
              Long id = userService.registerUser(user);
              RegistrationResult registrationResult = new RegistrationResult();
              registrationResult.setId(id);
              return registrationResult;
          }
    }
    複製程式碼
  5. Dubbox與Spring整合配置:msa-demo-provider.xml

     <?xml version="1.0" encoding="UTF-8"?>
     <beans xmlns="http://www.springframework.org/schema/beans"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
         xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
         http://code.alibabatech.com/schema/dubbo
         http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    
         <!-- 當前應用資訊配置 -->
         <dubbo:application name="msa-demo-provider" owner="tbr" organization="tbr"/>
         <dubbo:monitor address="x.x.x.x:20884"/>
    
         <!-- 多註冊中心配置,豎號分隔表示同時連線多個不同註冊中心,同一註冊中心的多個叢集地址用逗號分隔 -->
         <dubbo:registry protocol="zookeeper" address="x.x.x.x:2181,x.x.x.x:2181,x.x.x.x:2181"/>
         <dubbo:protocol name="dubbo" port="20880" serialization="kryo"/>
    
         <!--
            1. 選用了嵌入式的jetty來做rest server,同時,如果不配置server屬性,rest協議預設也是選用jetty。
            jetty是非常成熟的java servlet容器,並和dubbo已經有較好的整合(目前5種嵌入式server中只有jetty
            和後面所述的tomcat、tjws,與dubbo監控系統等完成了無縫的整合),所以,如果你的dubbo系統是單獨啟動的程式,
            你可以直接預設採用jetty即可。dubbo中的rest協議預設將採用80埠.
            <dubbo:protocol name="rest" server="jetty"/>
     
            2. 配置選用了嵌入式的tomcat來做rest server。在嵌入式tomcat上,REST的效能比jetty上要好得多(參見後面的基準測試),
            建議在需要高效能的場景下采用tomcat。
            <dubbo:protocol name="rest" server="tomcat"/>
     
            3. 配置選用嵌入式的netty來做rest server。
            <dubbo:protocol name="rest" server="netty"/>
     
            4. 配置選用嵌入式的tjws或Sun HTTP server來做rest server。這兩個server實現非常輕量級,
            非常方便在整合測試中快速啟動使用,當然也可以在負荷不高的生產環境中使用。 注:tjws目前已經
            被deprecated掉了,因為它不能很好的和servlet 3.1 API工作。
            <dubbo:protocol name="rest" server="tjws"/>
            <dubbo:protocol name="rest" server="sunhttp"/>
    
            5. 如果你的dubbo系統不是單獨啟動的程式,而是部署到了Java應用伺服器中,則建議你採用以下配置:
            <dubbo:protocol name="rest" server="servlet"/>
    
            6. 通過將server設定為servlet,dubbo將採用外部應用伺服器的servlet容器來做rest server。同時,還要在dubbo系統的web.xml中新增如下配置:
            <web-app>
                <context-param>
                    <param-name>contextConfigLocation</param-name>
                    <param-value>/WEB-INF/classes/META-INF/spring/dubbo-demo-provider.xml</param-value>
                </context-param>
     
                <listener>
                    <listener-class>com.alibaba.dubbo.remoting.http.servlet.BootstrapListener</listener-class>
                </listener>
     
                <listener>
                    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
                </listener>
     
                <servlet>
                    <servlet-name>dispatcher</servlet-name>
                    <servlet-class>com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class>
                    <load-on-startup>1</load-on-startup>
                </servlet>
     
                <servlet-mapping>
                    <servlet-name>dispatcher</servlet-name>
                    <url-pattern>/*</url-pattern>
                </servlet-mapping>
            </web-app>
            即必須將dubbo的BootstrapListener和DispatherServlet新增到web.xml,以完成dubbo的REST功能與外部servlet容器的整合。
     
            其實,這種場景下你依然可以堅持用嵌入式server,但外部應用伺服器的servlet容器往往比嵌入式server更加強大
            (特別是如果你是部署到更健壯更可伸縮的WebLogic,WebSphere等),另外有時也便於在應用伺服器做統一管理、監控等等。
     
            如果將dubbo REST部署到外部Tomcat上,並配置server="servlet",即啟用外部的tomcat來做為rest server的底層實現,
            則最好在tomcat上新增如下配置:
            <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
                connectionTimeout="20000"
                redirectPort="8443"
                minSpareThreads="20"
                enableLookups="false"
                maxThreads="100"
                maxKeepAliveRequests="-1"
                keepAliveTimeout="60000"/>
            特別是maxKeepAliveRequests="-1",這個配置主要是保證tomcat一直啟用http長連線,以提高REST呼叫效能。
            但是請注意,如果REST消費端不是持續的呼叫REST服務,則一直啟用長連線未必是最好的做法。另外,一直啟用長連
            接的方式一般不適合針對普通webapp,更適合這種類似rpc的場景。所以為了高效能,在tomcat中,dubbo REST應
            用和普通web應用最好不要混合部署,而應該用單獨的例項。
     
            7. 注意:如果你是用spring的ContextLoaderListener來載入spring,
            則必須保證BootstrapListener配置在ContextLoaderListener之前,否則dubbo初始化會出錯。
         -->
     
         <!--
            1. 設定一個所有rest服務都適用的基礎相對路徑,即java web應用中常說的context path。只需要新增如下contextpath屬性即可.
            <dubbo:protocol name="rest" port="8888" keepalive="true" server="netty" iothreads="5" threads="100" contextpath="services"/>
     
            2. 可以為rest服務配置執行緒池大小:
            <dubbo:protocol name="rest" threads="500"/>
            注意:目前執行緒池的設定只有當server="netty"或者server="jetty"或者server="tomcat"的時候才能生效。另外,如果server="servlet",由於這時候啟用
            的是外部應用伺服器做rest server,不受dubbo控制,所以這裡的執行緒池設定也無效。
     
            如果是選用netty server,還可以配置Netty的IO worker執行緒數:
            <dubbo:protocol name="rest" iothreads="5" threads="100"/>
     
            3. 注意:如果你是選用外部應用伺服器做rest server, 即配置:
            <dubbo:protocol name="rest" port="8888" contextpath="services" server="servlet"/>
            則必須保證這裡設定的port、contextpath,與外部應用伺服器的埠、DispatcherServlet的上下文路徑(即webapp path加上servlet url pattern)保持一致。
     
            4. Dubbo中的rest服務預設都是採用http長連線來訪問,如果想切換為短連線,直接配置:
            <dubbo:protocol name="rest" keepalive="false"/>
            注意:這個配置目前只對server="netty"和server="tomcat"才能生效。
     
            5. 配置伺服器提供端所能同時接收的最大HTTP連線數,防止REST server被過多連線撐爆,以作為一種最基本的自我保護機制:
            <dubbo:protocol name="rest" accepts="500" server="tomcat/>
            注意:這個配置目前只對server="tomcat"才能生效。
     
            6. 如果rest服務的消費端也是dubbo系統,可以像其他dubbo RPC機制一樣,配置消費端呼叫此rest服務的最大超時時間以及每個消費端所能啟動的最大HTTP連線數。
            <dubbo:service interface="xxx" ref="xxx" protocol="rest" timeout="2000" connections="10"/>
            當然,由於這個配置針對消費端生效的,所以也可以在消費端配置:
            <dubbo:reference id="xxx" interface="xxx" timeout="2000" connections="10"/>
            但是,通常我們建議配置在服務提供端提供此類配置。按照dubbo官方文件的說法:“Provider上儘量多配置Consumer端的屬性,讓Provider實現者一開始就思考Provider服務特點、服務質量的問題。”
            注意:如果dubbo的REST服務是釋出給非dubbo的客戶端使用,則這裡<dubbo:service/>上的配置完全無效,因為這種客戶端不受dubbo控制。
    
            7. Dubbo的REST支援用GZIP壓縮請求和響應的資料,以減少網路傳輸時間和頻寬佔用,但這種方式會也增加CPU開銷。
         -->
    
         <!--
            1. use tomcat server
            2. 用rest協議在8888埠暴露服務
            3. Dubbo的REST也支援JAX-RS標準的Filter和Interceptor,以方便對REST的請求與響應過程做定製化的攔截處理。
               其中,Filter主要用於訪問和設定HTTP請求和響應的引數、URI等等。如:CacheControlFilter.java
               Interceptor主要用於訪問和修改輸入與輸出位元組流,例如,手動新增GZIP壓縮.如:GZIPWriterInterceptor.java
            4. 在標準JAX-RS應用中,我們一般是為Filter和Interceptor新增@Provider annotation,然後JAX-RS runtime會
               自動發現並啟用它們。而在dubbo中,我們是通過新增XML配置的方式來註冊Filter和Interceptor.
            5. 在此,我們可以將Filter、Interceptor和DynamicFuture這三種型別的物件都新增到extension屬性上,多個之間用逗號分隔。(DynamicFuture是另一個介面,可以方便我們更動態的啟用Filter和Interceptor,感興趣請自行google。)
            6. 當然,dubbo自身也支援Filter的概念,但我們這裡討論的Filter和Interceptor更加接近協議實現的底層,
            相比dubbo的filter,可以做更底層的定製化。
            注:這裡的XML屬性叫extension,而不是叫interceptor或者filter,是因為除了Interceptor和Filter,未來我們
            還會新增更多的擴充套件型別。
            7. 如果REST的消費端也是dubbo系統(參見下文的討論),則也可以用類似方式為消費端配置Interceptor和Filter。但注
            意,JAX-RS中消費端的Filter和提供端的Filter是兩種不同的介面。例如前面例子中服務端是ContainerResponseFilter介面,
            而消費端對應的是ClientResponseFilter.
            8. Dubbo的REST也支援JAX-RS標準的ExceptionMapper,可以用來定製特定exception發生後應該返回的HTTP響應。
            9. Dubbo rest支援輸出所有HTTP請求/響應中的header欄位和body訊息體。LoggingFilter
         -->
         <dubbo:protocol name="rest" port="8888" threads="500" contextpath="services" server="tomcat" accepts="500"
                     extension="com.alibaba.dubbo.demo.extension.TraceInterceptor,
                     com.alibaba.dubbo.demo.extension.TraceFilter,
                     com.alibaba.dubbo.demo.extension.ClientTraceFilter,
                     com.alibaba.dubbo.demo.extension.DynamicTraceBinding,
                     com.alibaba.dubbo.demo.extension.CustomExceptionMapper,
                     com.alibaba.dubbo.rpc.protocol.rest.support.LoggingFilter"/>
    
         <!--
            use the external tomcat or other server with the servlet approach; the port and contextpath must be exactly the same as those in external server
            <dubbo:protocol name="rest" port="8888" contextpath="services" server="servlet"/>
         -->
     
         <dubbo:protocol name="http" port="8889"/>
         <dubbo:protocol name="hessian" port="8890"/>
         <dubbo:protocol name="webservice" port="8892"/>
      
         <!-- 宣告需要暴露的服務介面 -->
         <dubbo:service interface="com.alibaba.dubbo.demo.user.UserService" ref="userService" protocol="dubbo" group="xmlConfig"/>
     
         <!--
            1. 為了和其他dubbo遠端呼叫協議保持一致,在rest中作校驗的annotation必須放在服務的介面上,
            把annotation放在介面上至少有一個好處是,dubbo的客戶端可以共享這個介面的資訊,dubbo甚
            至不需要做遠端呼叫,在本地就可以完成輸入校驗。
    
            然後按照dubbo的標準方式在XML配置中開啟驗證:
            <dubbo:service interface=xxx.UserService" ref="userService" protocol="rest" validation="true"/>
    
            2. 在dubbo的其他很多遠端呼叫協議中,如果輸入驗證出錯,是直接將RpcException拋向客戶端,而在rest中由於客戶端經常是非dubbo,甚至非java的系統,所以不便直接丟擲Java異常。因此,目前我們將校驗錯誤以XML的格式返回:
            <violationReport>
                <constraintViolations>
                    <path>getUserArgument0</path>
                    <message>User ID must be greater than 1</message>
                    <value>0</value>
                </constraintViolations>
            </violationReport>
     
            如果你認為預設的校驗錯誤返回格式不符合你的要求,可以如上面章節所述,新增自定義的ExceptionMapper來自由的定製錯誤返回格式。
            需要注意的是,這個ExceptionMapper必須用泛型宣告來捕獲dubbo的RpcException,才能成功覆蓋dubbo rest預設的異常處理策略。
            為了簡化操作,其實這裡最簡單的方式是直接繼承dubbo rest的RpcExceptionMapper,並覆蓋其中處理校驗異常的方法即可.
         -->
         <dubbo:service interface="com.alibaba.dubbo.demo.user.facade.AnotherUserRestService" ref="anotherUserRestService" protocol="rest" timeout="2000" connections="100" validation="true"/>
      
         <bean id="userService" class="com.alibaba.dubbo.demo.user.UserServiceImpl"/>
     
         <bean id="anotherUserRestService" class="com.alibaba.dubbo.demo.user.facade.AnotherUserRestServiceImpl">
             <property name="userService" ref="userService"/>
         </bean>
     
         <!--
            對於jax-rs和spring mvc,其實我對spring mvc的rest支援還沒有太深入的看過,說點初步想法,請大家指正:
     
            spring mvc也支援annotation的配置,其實和jax-rs看起來是非常非常類似的。
     
            我個人認為spring mvc相對更適合於面向web應用的restful服務,比如被AJAX呼叫,也可能輸出HTML之類的,應用中還
            有頁面跳轉流程之類,spring mvc既可以做好正常的web頁面請求也可以同時處理rest請求。但總的來說這個restful服務
            是在展現層或者叫web層之類實現的
    
            而jax-rs相對更適合純粹的服務化應用,也就是傳統Java EE中所說的中間層服務,比如它可以把傳統的EJB釋出成restful
            服務。在spring應用中,也就把spring中充當service之類的bean直接釋出成restful服務。總的來說這個restful服務是
            在業務、應用層或者facade層。而MVC層次和概念在這種做比如(後臺)服務化的應用中通常是沒有多大價值的。
    
            當然jax-rs的有些實現比如jersey,也試圖提供mvc支援,以更好的適應上面所說的web應用,但應該是不如spring mvc。
    
            在dubbo應用中,我想很多人都比較喜歡直接將一個本地的spring service bean(或者叫manager之類的)完全透明的釋出
            成遠端服務,則這裡用JAX-RS是更自然更直接的,不必額外的引入MVC概念。當然,先不討論透明發布遠端服務是不是最佳實踐,
            要不要新增facade之類。
    
            當然,我知道在dubbo不支援rest的情況下,很多朋友採用的架構是spring mvc restful呼叫dubbo (spring) service
            來發布restful服務的。這種方式我覺得也非常好,只是如果不修改spring mvc並將其與dubbo深度整合,restful服務不能
            像dubbo中的其他遠端呼叫協議比如webservices、dubbo rpc、hessian等等那樣,享受諸多高階的服務治理的功能,比如:
            註冊到dubbo的服務註冊中心,通過dubbo監控中心監控其呼叫次數、TPS、響應時間之類,通過dubbo的統一的配置方式控制其
            比如執行緒池大小、最大連線數等等,通過dubbo統一方式做服務流量控制、許可權控制、頻次控制。另外spring mvc僅僅負責服務
            端,而在消費端,通常是用spring restTemplate,如果restTemplate不和dubbo整合,有可能像dubbo服務客戶端那樣自動
            或者人工干預做服務降級。如果服務端消費端都是dubbo系統,通過spring的rest互動,如果spring rest不深度整合dubbo,
            則不能用dubbo統一的路由分流等功能。
    
            當然,其實我個人認為這些東西不必要非此即彼的。我聽說spring創始人rod johnson總是愛說一句話,
            the customer is always right,其實與其非要探討哪種方式更好,不如同時支援兩種方式就是了,
            所以原來在文件中也寫過計劃支援spring rest annoation,只是不知道具體可行性有多高。
     
            1. JAX-RS中過載的方法能夠對映到同一URL地址嗎?
            http://stackoverflow.com/questions/17196766/can-resteasy-choose-method-based-on-query-params
     
            2. JAX-RS中作POST的方法能夠接收多個引數嗎?
            http://stackoverflow.com/questions/5553218/jax-rs-post-multiple-objects
     
            注:以上備註,均來自:https://dangdangdotcom.github.io/dubbox/rest.html
         -->
    </beans>
    複製程式碼
  6. 配置dubbo.properties

    #dubbo.container=log4j,spring
    #dubbo.application.name=demo-provider
    #dubbo.application.owner=
    #dubbo.registry.address=multicast://224.5.6.7:1234
    #dubbo.registry.address=zookeeper://127.0.0.1:2181
    #dubbo.registry.address=redis://127.0.0.1:6379
    #dubbo.registry.address=dubbo://127.0.0.1:9090
    #dubbo.monitor.protocol=registry
    #dubbo.protocol.name=dubbo
    #dubbo.protocol.port=20880
    #dubbo.service.loadbalance=roundrobin
    #dubbo.log4j.file=logs/msa-demo-provider.log
    #dubbo.log4j.level=INFO
    #dubbo.log4j.subdirectory=20880
    dubbo.application.logger=slf4j
    dubbo.spring.config=classpath*:msa-*.xml
    複製程式碼

1.4 服務啟動

定義服務啟動類

package com.alibaba.dubbo.demo.provider;
/**
  * @author TaoBangren
  * @version 1.0
  * @since 2017/5/17 上午9:26
  */
public class DemoProvider {
      public static void main(String[] args) {
          com.alibaba.dubbo.container.Main.main(args);
      }
}
複製程式碼

執行main方法啟動,看到以下日誌輸出時,msa-demo-provider啟動成功:

msa-demo-provider啟動成功

檢視DubboKeeper監控大盤,msa-demo-provider釋出服務成功,可以看到我們釋出的兩個介面:

msa-demo-provider釋出服務成功

2. 客戶端

  1. 建立Maven模組:msa-demo-client

    msa-demo-client

  2. msa-demo-client:配置pom.xml

    <!-- Module依賴 START -->
    <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>msa-demo-api</artifactId>
         <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- Module依賴 END -->
    複製程式碼
  3. Dubbox與Spring整合配置:msa-demo-client.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://code.alibabatech.com/schema/dubbo
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    
       <!-- 當前應用資訊配置 -->
       <!--<dubbo:application name="msa-demo-client" owner="shark" organization="shark"/>-->
    
       <!-- 多註冊中心配置,豎號分隔表示同時連線多個不同註冊中心,同一註冊中心的多個叢集地址用逗號分隔 -->
       <!--<dubbo:registry protocol="zookeeper" address="x.x.x.x:2181,x.x.x.x:2181,x.x.x.x:2181"/>-->
       <!--<dubbo:monitor address="x.x.x.x:20884"/>-->
     
       <dubbo:reference id="userService" interface="com.alibaba.dubbo.demo.user.UserService" group="xmlConfig"/>
       <dubbo:reference id="anotherUserRestService" interface="com.alibaba.dubbo.demo.user.facade.AnotherUserRestService"/>
    
       <!--
           directly connect to provider to simulate the access to non-dubbo rest services
           <dubbo:reference id="anotherUserRestService" interface="com.alibaba.dubbo.demo.user.facade.AnotherUserRestService" url="rest://localhost:8888/services/"/>
       -->
    </beans>
    複製程式碼

3. 消費端

3.1 消費端實現

  1. 建立Maven模組:msa-demo-consumer

    msa-demo-consumer

  2. msa-demo-consumer:配置pom.xml

    <!-- Module依賴 START -->
    <dependency>
          <groupId>com.jeasy</groupId>
          <artifactId>msa-demo-client</artifactId>
          <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- Module依賴 END -->
    複製程式碼
  3. 建立消費端測試類:DemoAction.java

    package com.alibaba.dubbo.demo;
    import com.alibaba.dubbo.rpc.RpcContext;
    import com.alibaba.dubbo.demo.user.User;
    import com.alibaba.dubbo.demo.user.UserService;
    import com.alibaba.dubbo.demo.user.facade.AnotherUserRestService;
    /**
     * @author TaoBangren
     * @version 1.0
     * @since 2017/5/17 上午9:26
     */
    public class DemoAction {
    
          private UserService userService;
    
          private AnotherUserRestService anotherUserRestService;
    
          public void setUserService(final UserService userService) {
              this.userService = userService;
          }
    
          public void setAnotherUserRestService(final AnotherUserRestService anotherUserRestService) {
              this.anotherUserRestService = anotherUserRestService;
          }
    
          public void start() throws Exception {
              User user = new User();
              user.setId(1L);
              user.setName("larrypage");
    
              System.out.println("SUCCESS: registered user with id by rest" + anotherUserRestService.registerUser(user).getId());
              System.out.println("SUCCESS: registered user with id " + userService.registerUser(user));
     
              RpcContext.getContext().setAttachment("clientName", "demo");
              RpcContext.getContext().setAttachment("clientImpl", "dubbox rest");
              System.out.println("SUCCESS: got user by rest" + anotherUserRestService.getUser(1L));
              System.out.println("SUCCESS: got user " + userService.getUser(1L));
          }
    }
    複製程式碼
  4. Dubbox與Spring整合配置:msa-demo-consumer.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://code.alibabatech.com/schema/dubbo
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    
       <!-- 當前應用資訊配置 -->
       <dubbo:application name="msa-demo-consumer" owner="tbr" organization="tbr"/>
    
       <!-- 多註冊中心配置,豎號分隔表示同時連線多個不同註冊中心,同一註冊中心的多個叢集地址用逗號分隔 -->
       <dubbo:registry protocol="zookeeper" address="x.x.x.x:2181,x.x.x.x:2181,x.x.x.x:2181"/>
       <dubbo:monitor address="x.x.x.x:20884"/>
    
       <bean class="com.alibaba.dubbo.demo.DemoAction" init-method="start">
           <property name="userService" ref="userService"/>
           <property name="anotherUserRestService" ref="anotherUserRestService"/>
       </bean>
    </beans>
    複製程式碼
  5. 配置dubbo.properties

    #dubbo.container=log4j,spring
    #dubbo.application.name=demo-consumer
    #dubbo.application.owner=
    #dubbo.registry.address=multicast://224.5.6.7:1234
    #dubbo.registry.address=zookeeper://127.0.0.1:2181
    #dubbo.registry.address=redis://127.0.0.1:6379
    #dubbo.registry.address=dubbo://127.0.0.1:9090
    #dubbo.monitor.protocol=registry
    #dubbo.log4j.file=logs/msa-demo-consumer.log
    #dubbo.log4j.level=INFO
    dubbo.application.logger=slf4j
    dubbo.spring.config=classpath*:msa-*.xml
    複製程式碼

3.2 消費端測試

定義消費啟動類:

package com.jeasy;
/**
 * @author TaoBangren
 * @version 1.0
 * @since 2017/5/17 上午9:26
 */
public class DemoConsumer {
      public static void main(String[] args) {
          com.alibaba.dubbo.container.Main.main(args);
      }
}
複製程式碼

執行main方法啟動,看到以下日誌輸出時,msa-demo-consumer啟動成功:

msa-demo-consumer啟動成功

同時服務端會輸出服務呼叫日誌資訊,並呼叫成功,如下:

服務端呼叫日誌

4. 規範使用

模組 描述 是否必須
msa-xxx-api 定義介面&實體 必須
msa-xxx-provider 依賴api模組,實現服務介面,提供服務 必須
msa-xxx-client 依賴api模組,Spring配置檔案&測試用例,提供給第三方呼叫服務使用 必須
msa-xxx-consumer 依賴client模組,建議保留該模組,避免client模組直接與應用方緊耦合 可選

5. 推薦閱讀

5.1 Dubbox相關資源

  1. 原始碼地址 : github.com/dangdangdot…
  2. 在Dubbo中開發REST風格的遠端呼叫 : dangdangdotcom.github.io/dubbox/rest…
  3. 在Dubbo中使用高效的Java序列化 : dangdangdotcom.github.io/dubbox/seri…
  4. 使用JavaConfig方式配置dubbox : dangdangdotcom.github.io/dubbox/java…
  5. Dubbo Jackson序列化使用說明 : dangdangdotcom.github.io/dubbox/jack…
  6. Demo : dangdangdotcom.github.io/dubbox/demo…
  7. 噹噹網開源Dubbox,擴充套件Dubbo服務框架支援REST風格遠端呼叫 : www.infoq.com/cn/news/201…
  8. Dubbox Wiki : github.com/dangdangdot…

5.2 Dubbo相關資源

  1. 原始碼地址 : github.com/alibaba/dub…
  2. Dubbo Wiki : github.com/alibaba/dub…
  3. http://dubbo.io/