【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 1965704869@qq.com
 * @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 1965704869@qq.com
 * @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 1965704869@qq.com
 * @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 1965704869@qq.com
 * @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底层架构的学习。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值