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中的配置步骤是:
- 打开Project Structure,
- 在弹出的窗口按照下图依次勾选:
- 最后在弹出的窗口选择你要依赖的模块,这里选择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底层架构的学习。