下图为dubbo框架大图,根据dubbo的分层结构,结合源码对每一层的内容进行源码分析:
一、Service层:
dubbo的service层主要对外暴露Interface的,服务提供方实现接口的业务逻辑,并将接口对应的服务暴露给服务注册中心,以下是服务消费者和服务提供者对应的service配置方式:
定义接口:
package org.apache.dubbo.demo;
import java.util.concurrent.CompletableFuture;
public interface DemoService {
String sayHello(String name);
default CompletableFuture<String> sayHelloAsync(String name) {
return CompletableFuture.completedFuture(sayHello(name));
}
}
(1)、服务提供者:
基于注解的服务提供者:
package org.apache.dubbo.demo.provider;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.demo.DemoService;
import org.apache.dubbo.rpc.RpcContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CompletableFuture;
/**
* 注解的实现方式暴露服务:
* 通过在实现类上添加DubboService注解可以将
* 服务暴露到服务注册中心上
*/
@DubboService
public class DemoServiceImpl implements DemoService {
private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class);
@Override
public String sayHello(String name) {
logger.info("Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
return "Hello " + name + ", response from provider: " + RpcContext.getContext().getLocalAddress();
}
@Override
public CompletableFuture<String> sayHelloAsync(String name) {
return null;
}
}
服务启动类添加dubbo配置注解:
package org.apache.dubbo.demo.consumer;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.apache.dubbo.demo.DemoService;
import org.apache.dubbo.demo.consumer.comp.DemoServiceComponent;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
public class Application {
/**
* In order to make sure multicast registry works, need to specify '-Djava.net.preferIPv4Stack=true' before
* launch the application
*/
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
context.start();
DemoService service = context.getBean("demoServiceComponent", DemoServiceComponent.class);
String hello = service.sayHello("world");
System.out.println("result :" + hello);
}
@Configuration
@EnableDubbo(scanBasePackages = "org.apache.dubbo.demo.consumer.comp")
@PropertySource("classpath:/spring/dubbo-consumer.properties")
@ComponentScan(value = {"org.apache.dubbo.demo.consumer.comp"})
static class ConsumerConfiguration {
}
}
通过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://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="hello-world-app" />
<!-- 使用multicast广播注册中心暴露服务地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234" />
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" />
<!-- 和本地bean一样实现服务 -->
<bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl" />
</beans>
基于Java API方式暴露服务:
private static void startWithBootstrap() {
// 项目启动后,通过声明式 ServiceConfig 配置
ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
// 配置服务接口类
service.setInterface(DemoService.class);
// 添加服务接口对应的实现类对象
service.setRef(new DemoServiceImpl());
DubboBootstrap bootstrap = DubboBootstrap.getInstance();
bootstrap.application(new ApplicationConfig("dubbo-demo-api-provider"))
.registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
.service(service)
.start()
.await();
}
(2)、服务消费者:
基于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://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
<dubbo:application name="consumer-of-helloworld-app" />
<!-- 使用multicast广播注册中心暴露发现服务地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234" />
<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
<dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" />
</beans>
基于注解的服务发现:
package org.apache.dubbo.demo.consumer.comp;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.demo.DemoService;
import org.springframework.stereotype.Component;
import java.util.concurrent.CompletableFuture;
@Component("demoServiceComponent")
public class DemoServiceComponent implements DemoService {
/**
* 通过dubbo提供的DubboReference就能发现服务
* 并加载到spring容器中
*/
@DubboReference
private DemoService demoService;
@Override
public String sayHello(String name) {
return demoService.sayHello(name);
}
@Override
public CompletableFuture<String> sayHelloAsync(String name) {
return null;
}
}
通过Java API方式服务发现:
private static void runWithBootstrap() {
// 服务消费方通过 ReferenceConfig 显示引用服务注册中心的服务
ReferenceConfig<DemoService> reference = new ReferenceConfig<>();
// 添加服务对应的接口
reference.setInterface(DemoService.class);
reference.setGeneric("true");
DubboBootstrap bootstrap = DubboBootstrap.getInstance();
bootstrap.application(new ApplicationConfig("dubbo-demo-api-consumer"))
.registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
.reference(reference)
.start();
// 从换从中获取服务对应的实例
DemoService demoService = ReferenceConfigCache.getCache().get(reference);
String message = demoService.sayHello("dubbo");
System.out.println(message);
// generic invoke
GenericService genericService = (GenericService) demoService;
Object genericInvokeResult = genericService.$invoke("sayHello", new String[] { String.class.getName() },
new Object[] { "dubbo generic invoke" });
System.out.println(genericInvokeResult);
}
二、服务注册和服务引用流程
以下是dubbo服务提供者和服务消费者初始化以及调用的流程图:
三、Config配置层:
dubbo的config层主要分为服务提供者ServiceConfig类和服务消费者ReferenceConfig类,对外配置接口,以 ServiceConfig
, ReferenceConfig
为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类。
(1)、ServiceBean、ServiceConfig配置解析类:
基于 dubbo.jar 内的 META-INF/spring.handlers
配置,Spring 在遇到 dubbo 名称空间时,会回调 DubboNamespaceHandler
。所有 dubbo 的标签,都统一用 DubboBeanDefinitionParser
进行解析,基于一对一属性映射,将 XML 标签解析为 Bean 对象。在 ServiceConfig.export()
或 ReferenceConfig.get()
初始化时,将 Bean 对象转换 URL 格式,所有 Bean 属性转成 URL 的参数。然后将 URL 传给 协议扩展点,基于扩展点的 扩展点自适应机制,根据 URL 的协议头,进行不同协议的服务暴露或引用。
1. 只暴露服务端口:
在没有注册中心,直接暴露提供者的情况下 1,ServiceConfig
解析出的 URL 的格式为: dubbo://service-host/com.foo.FooService?version=1.0.0
。基于扩展点自适应机制,通过 URL 的 dubbo://
协议头识别,直接调用 DubboProtocol
的 export()
方法,打开服务端口。
2. 向注册中心暴露服务:
在有注册中心,需要注册提供者地址的情况下 2,ServiceConfig
解析出的 URL 的格式为: registry://registry-host/org.apache.dubbo.registry.RegistryService?export=URL.encode("dubbo://service-host/com.foo.FooService?version=1.0.0")
,基于扩展点自适应机制,通过 URL 的 registry://
协议头识别,就会调用 RegistryProtocol
的 export()
方法,将 export
参数中的提供者 URL,先注册到注册中心。再重新传给 Protocol
扩展点进行暴露: dubbo://service-host/com.foo.FooService?version=1.0.0
,然后基于扩展点自适应机制,通过提供者 URL 的 dubbo://
协议头识别,就会调用 DubboProtocol
的 export()
方法,打开服务端口。
上图是服务提供者暴露服务的主过程:
下面是通过调用ProxyFactory.getInvoker()方法,拿到Invoker对象的代码块, ServiceConfig类的doExportUrlsFor1Protocol方法
Dubbo 的实现
Dubbo 协议的 Invoker
转为 Exporter
发生在 DubboProtocol
类的 export
方法,它主要是打开 socket 侦听服务,并接收客户端发来的各种请求,通讯细节由 Dubbo 自己实现。
RMI 的实现
RMI 协议的 Invoker
转为 Exporter
发生在 RmiProtocol
类的 export
方法,它通过 Spring 或 Dubbo 或 JDK 来实现 RMI 服务,通讯细节这一块由 JDK 底层来实现,这就省了不少工作量。
(2)、ReferenceBean、ReferenceConfig配置解析类:
上图是服务消费的主过程:
首先 ReferenceConfig
类的 init
方法调用 Protocol
的 refer
方法生成 Invoker
实例(如上图中的红色部分),这是服务消费的关键。接下来把 Invoker
转换为客户端需要的接口(如:HelloWorld)。
关于每种协议如 RMI/Dubbo/Web service 等它们在调用 refer
方法生成 Invoker
实例的细节和上一章节所描述的类似。