【Dubbo】深入理解Apache Dubbo(二):開發第一個Dubbo應用程序

1. 引言

本篇側重用代碼說明Dubbo應用程序的開發及使用,通過一個簡單的示例演示Dubbo應用。讀者要是對Dubbo框架的基本概念還有模糊,可以先閱讀
【Dubbo】深入理解Apache Dubbo(一):帶你走近高性能RPC通信框架
另外本篇涉及的所有源碼都已經上傳到GitHub,歡迎交流學習:
Dubbo應用程序實戰

2. 環境配置

無論你使用的是Windows、Linux還是Mac OS操作系統,請保證已經配置如下的環境:JDK(必須,推薦1.8)、IDE(推薦IDEA)、Maven(推薦版本3.x.x)以及ZooKeeper(不是必須,但生產環境已經大量使用ZooKeeper作爲註冊中心,爲了深入理解Dubbo,建議配置)
另外,爲了更好的學習Dubbo,強烈建議clone源碼學習,附GitHub地址:
https://github.com/apache/dubbo
通過git clone https://github.com/apache/dubbo命令將Dubbo源碼clone到本地之後,用IDEA打開。所有模塊的測試都在對應的test文件夾下,可以在對應的源碼中打好斷點,然後利用測試類進行單元調試,有利於更加深入的理解Dubbo。

3. 開發Dubbo應用程序

在本節中,筆者將動手快速構建一個完整的服務器和客戶端程序。程序功能很簡單:服務器接收客戶端的請求,然後將消息不做任何處理返回給客戶端。但是麻雀雖小五臟俱全,這個示例將很有助於我們理解Dubbo的運行過程。應用程序編寫有三種方式:XML、註解和API。

3.1 基於XML配置的方式

1.編寫服務端
先定義服務暴露的接口EchoService
程序清單3-1

/**
 * @author Carson Chu
 * @email [email protected]
 * @date 2020/1/31 13:53
 * @description
 */
public interface EchoService {

    /**
     * @description 發送給客戶端的信息
     * @params [msg]
     * @returns java.lang.String
     */
    String productMsg(String msg);
}

然後實現該接口。
程序清單3-2

/**
 * @author Carson Chu
 * @email [email protected]
 * @date 2020/1/31 13:53
 * @description
 */
public class EchoServiceImpl implements EchoService {
    @Override
    public String productMsg(String msg) {
        String curTime = new SimpleDateFormat("HH:mm:ss").format(new Date());
        System.out.println("[" + curTime + "] Hello " + msg
                + ", request from client: " + RpcContext.getContext().getRemoteAddress());
        return msg;
    }
}

接下來,就是基於XML配置的核心了,將配置文件echo-provider.xml放到項目資源目錄resources/spring下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- 服務提供方應用名稱, 方便用於依賴跟蹤 -->
    <dubbo:application name="echo-provider"/>

    <!-- 使用本地zookeeper作爲註冊中心 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <!-- 只用Dubbo協議並且指定監聽端口 20880 -->
    <dubbo:protocol name="dubbo" port="20880"/>

    <!-- 通過xml方式配置爲bean, 讓spring託管和實例化 -->
    <bean id="echoService" class="com.pers.server.impl.AnnotationServiceImpl"/>

    <!-- 聲明服務暴露的接口,並暴露服務 -->
    <dubbo:service interface="com.pers.server.AnnotationService" ref="echoService"/>

</beans>

之後,我們還需要編寫一個類用於加載該配置文件並啓動Dubbo服務:
程序清單3-3

/**
 * @author Carson Chu
 * @description
 */
public class EchoProvider {

    public static void main(String[] args) throws Exception {
        // #1 指定服務暴露配置文件
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"spring/echo-provider.xml"});
        // #2 啓動spring容器並暴露服務
        context.start();

        System.in.read();
    }
}

2.編寫客戶端
客戶端也是通過加載XML配置文件的:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- 服務消費方應用名稱, 方便用於依賴跟蹤 -->
    <dubbo:application name="echo-consumer"/>

    <!-- 使用本地zookeeper作爲註冊中心 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <!-- 指定要消費的服務 -->
    <dubbo:reference id="echoService" check="false" interface="com.pers.server.EchoService"/>

</beans>

但此步需要保證client依賴server端的服務(因爲EchoService屬於server模塊),在IDEA中的配置步驟是:

  1. 打開Project Structure,
    在這裏插入圖片描述
  2. 在彈出的窗口按照下圖依次勾選:
    在這裏插入圖片描述
  3. 最後在彈出的窗口選擇你要依賴的模塊,這裏選擇dubbo-server:

在這裏插入圖片描述
配置完成之後,開始編寫客戶端消費程序:
程序清單3-4

/**
 * @author Carson Chu
 * @description
 */
public class EchoConsumer {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"spring/echo-consumer.xml"});
        context.start();
        EchoService echoService = (EchoService) context.getBean("echoService"); // get remote service proxy

        String status = echoService.productMsg("Hello Dubbo!");
        System.out.println("echo result: " + status);
    }
}

編寫完成之後運行main方法,可以在控制檯看到運行結果:
在這裏插入圖片描述
到這裏,基於XML的方式編寫Dubbo應用程序就完成了。
3. 可能遇到的問題
如果啓動報錯:

java.lang.NoClassDefFoundError: io/netty/channel/nio/NioEventLoopGroup

則需要引入該jar包:

            <dependency>
                <groupId>io.netty</groupId>
                <artifactId>netty-all</artifactId>
                <version>4.1.42.Final</version>
            </dependency>

3.2 基於註解的方式

通過XML方式配置啓動Dubbo服務比較常見,另外Dubbo也支持通過註解的方式啓動。通過註解的方式更加友好一點,雖然會耦合一些Dubbo框架自身的註解,但是代碼重構的時候比較便利。
1. 編寫服務端
該方式在定義了接口之後,只需要在接口實現類上加上註解@Service。需要注意的是,**這個@Service不是Spring框架的註解,而是Dubbo的註解。**使用註解之後,由Dubbo框架bazhege 實現類升級爲Spring容器的Bean。
程序清單3-5

import com.alibaba.dubbo.config.annotation.Service;

/**
 * @author Carson Chu
 * @description
 */
@Service
public class AnnotationServiceImpl implements AnnotationService {
    @Override
    public String productMsg(String msg) {
        String curTime = new SimpleDateFormat("HH:mm:ss").format(new Date());
        System.out.println("[" + curTime + "] Hello " + msg
                + ", request from client: " + RpcContext.getContext().getRemoteAddress());
        return msg;
    }
}

不同於基於XML的方式,基於註解的方式生成Dubbo的配置信息是通過程序以及註解來實現的。
程序清單3-6

/**
 * @author Carson Chu
 * @email [email protected]
 * @date 2020/1/31 15:30
 * @description
 */
public class AnnotationProvider {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
        context.start();
        System.in.read();
    }

    @Configuration
    // #1 指定掃描服務的位置
    @EnableDubbo(scanBasePackages = "com.pers.server")
    static class ProviderConfiguration {
        @Bean
        public ProviderConfig providerConfig() {
            return new ProviderConfig();
        }

        @Bean
        public ApplicationConfig applicationConfig() {
            ApplicationConfig applicationConfig = new ApplicationConfig();
            applicationConfig.setName("echo-annotation-provider");
            return applicationConfig;
        }

        @Bean
        public RegistryConfig registryConfig() {
            RegistryConfig registryConfig = new RegistryConfig();
            // #2 使用zookeeper作爲註冊中心,同時給出註冊中心ip和端口
            registryConfig.setProtocol("zookeeper");
            registryConfig.setAddress("localhost");
            registryConfig.setPort(2181);
            return registryConfig;
        }

        @Bean
        public ProtocolConfig protocolConfig() {
            ProtocolConfig protocolConfig = new ProtocolConfig();
            // #3 默認服務使用dubbo協議,在20880端口監聽服務
            protocolConfig.setName("dubbo");
            protocolConfig.setPort(20880);
            return protocolConfig;
        }
    }
}

2. 編寫客戶端
通過註解消費服務的時候,只需要@Reference註解標註,該註解適用於對象字段和方法。
程序清單3-7 基於註解包裝消費

@Component
public class EchoConsumer {
    @Reference
    private AnnotationService annotationService;

    public String produceMsg(String msg) {
        return annotationService.productMsg(msg);
    }
}

在完成上述的消費定義之後,還需要完成基於註解的啓動代碼。
程序清單3-7 基於註解消費服務

/**
 * @author Carson Chu
 * @email [email protected]
 * @date 2020/1/31 17:40
 * @description
 */
public class AnnotationConsumer {
    public static void main(String[] args) {
        // #1 基於註解配置初始化spring上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
        context.start();
        // #2 發起服務調用
        EchoConsumer echoService = context.getBean(EchoConsumer.class);
        String hello = echoService.produceMsg("Hello Dubbo!");
        System.out.println("result: " + hello);
    }

    @Configuration
    // #3 指定要掃描的消費註解,會觸發注入
    @EnableDubbo(scanBasePackages = "com.pers.client")
    @ComponentScan(value = {"com.pers.client"})
    static class ConsumerConfiguration {
        @Bean
        public ApplicationConfig applicationConfig() {
            ApplicationConfig applicationConfig = new ApplicationConfig();
            applicationConfig.setName("echo-annotation-consumer");
            return applicationConfig;
        }

        @Bean
        public ConsumerConfig consumerConfig() {
            return new ConsumerConfig();
        }

        @Bean
        public RegistryConfig registryConfig() {
            RegistryConfig registryConfig = new RegistryConfig();
            // #4 使用zookeeper作爲註冊中心,同時給出註冊中心ip和端口
            registryConfig.setProtocol("zookeeper");
            registryConfig.setAddress("localhost");
            registryConfig.setPort(2181);
            return registryConfig;
        }
    }
}

然後啓動該服務,依然會看出控制檯輸出:result:Hello Dubbo!
3. 可能遇到的問題
如果運行的時候報錯:

NoClassDefFoundError: com/alibaba/spring/util/PropertySourcesUtils

則需要引入該jar包:

        <dependency>
            <groupId>com.alibaba.spring</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>1.0.5</version>
        </dependency>

3.3 基於API的方式

該方式在大部分場景下不會直接使用,但在開發網關類的應用時,該方式則非常有用。本篇對該方式不做贅述。感興趣的同學可以私聊我學習交流。

小結

通過以上的代碼,我們已經實現了利用Dubbo服務實現客戶端和服務端交互的功能。在客戶端啓動時發生了哪些事情呢?這裏做個小結:

  • 客戶端啓動時會創建和Zookeeper註冊中心的連接並拉取服務列表
  • 拉取服務列表完成之後,會與遠程服務建立TCP長連接
  • 客戶端發起服務調用,發送“Hello Dubbo!”給服務方,服務方不做任何處理返回給客戶端。
  • 客戶端收到回顯消息並輸出。

在實現了第一個Dubbo應用程序之後,從下一篇開始,筆者將會開始Dubbo底層架構的學習。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章