正常在项目中,我们都是在Spring环境下使用Dubbo,所以我们这里就在Spring的环境下看看Dubbo是如何运作的
入口
在源码下载下来之后,有一个dubbo-demo目录,里面有一个基于spring注解的子目录dubbo-demo-annotation, 里面有一个生产者的demo,还有一个消费者的demo
Provider下面的Application:
public class Application {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
context.start();
System.in.read();
}
/**
* 1、EnableDubbo
* 2、DubboBootstrapApplicationListener监听容器启动事件,然后调用DubboBootstrap的start方法
* 1、会导出DubboService
* 2、会订阅DubboService
* 3、
*/
@Configuration
@EnableDubbo(scanBasePackages = "org.apache.dubbo.demo.provider")
@PropertySource("classpath:/spring/dubbo-provider.properties")
static class ProviderConfiguration {
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
return registryConfig;
}
}
}
dubbo-provider.properties
Consumer下面的Application:
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 {
}
}
dubbo-consumer.properties
Provider#Application
@PropertySource(“classpath:/spring/dubbo-provider.properties”)
这个注解是Spring注解,负责解析配置文件,把解析到的文件放到Environment对象中。而Dubbo就是从这个对象里面拿到配置值,生成对应的对象,比如 dubbo.application.name会生成一个ApplicationConfig,比如dubbo.protocal.* 会生成一个ProtocalConfig对象
@EnableDubbo(scanBasePackages = “org.apache.dubbo.demo.provider”)
@EnableDubboConfig
完成对Dubbo配置的解析,把配置文件里面的内容解析成一个一个配置对象
import一个DubboConfigConfigurationRegistrar
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
/** 此方法主要作用:
* 会对Properties文件进行解析,主要完成的事情是根据Properties文件的每个配置项的前缀、参数名、参数值生成对应的Bean。
* 比如前缀为"dubbo.application"的配置项,会生成一个 ApplicationConfig 类型的BeanDefinition。
* 比如前缀为"dubbo.protocol"的配置项,会生成一个 ProtocolConfig 类型的BeanDefinition。
*
* 都有哪些配置类? 具体看:
* 1、DubboConfigConfiguration.Single
* 2、DubboConfigConfiguration.Multiple
*/
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
importingClassMetadata.getAnnotationAttributes(EnableDubboConfig.class.getName()));
boolean multiple = attributes.getBoolean("multiple");
// Single Config Bindings
// todo 在解析DubboConfigConfiguration.Single会注入ConfigurationBeanBindingsRegister
registerBeans(registry, DubboConfigConfiguration.Single.class);
if (multiple) { // Since 2.6.6 https://github.com/apache/dubbo/issues/3193
/**
* 默认情况下开启了multiple模式,multiple模式表示开启多配置模式,意思是这样的:
1、如果没有开启multiple模式,那么只支持配置一个dubbo.protocol,比如:
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
dubbo.protocol.host=0.0.0.0
2、如果开启了multiple模式,那么可以支持多个dubbo.protocol,比如:
dubbo.protocols.p1.name=dubbo
dubbo.protocols.p1.port=20880
dubbo.protocols.p1.host=0.0.0.0
dubbo.protocols.p2.name=http
dubbo.protocols.p2.port=8082
dubbo.protocols.p2.host=0.0.0.0
*/
registerBeans(registry, DubboConfigConfiguration.Multiple.class);
}
// Since 2.7.6 todo 看这里
registerCommonBeans(registry);
}
总结一下做了几件事:
- 先往Spring容器注册了一个DubboConfigConfiguration.Single.class, 导致上面有注解引入了@EnableConfigurationBeanBindings
在EnableConfigurationBeanBindings上面import了ConfigurationBeanBindingsRegister, 又是相似的套路
ConfigurationBeanBindingsRegister也实现了ImportBeanDefinitionRegistrar接口,所以也会在Spring启动的时候调用到,我们能看到它做了做了几步
- 从EnableConfigurationBeanBindings 中获取注解的值,然后在获取value的属性值,value对应的是EnableConfigurationBeanBinding 中的值
- 创建一个ConfigurationBeanBindingRegistrar�类,将environment设置到里面,然后调用registerConfigurationBeanDefinitions方法
registerConfigurationBeanDefinition这里做了这样几件事:
1、从注解中获取prefix(如果里面有占位符,需要从environment中获取)、type(返回是一个class值,比如ApplicationConfig)、multiple
2、registerConfigurationBeans -> PropertySourcesUtils.getSubProperties从配置文件中取出对应的配置
3、registerConfigurationBeans -> registerConfigurationBean:将对应的Config(比如ApplicationConfig)注册到spring容器中
4、!!!registerConfigurationBindingBeanPostProcessor(registry):注册了一个ConfigurationBeanBindingPostProcessor�, 为什么需要这个类,因为目前我们只是把配置类做成BD放到Spring容器中,但是我们并没有赋值,所以需要在这个后置处理器中给这些BD赋值
- 如果开启了multiple, 会往spring中注册一个DubboConfigConfiguration.Multiple.class
- 调用registerCommonBeans, 往Spring容器中配置很多Dubbo需要的Bean,比如ReferenceAnnotationBeanPostProcessor�(处理@dubbo_reference注解的)、DubboApplicationListenerRegistrar�(用来创建很多的Listener,在spring启动过程中,dubbo需要做的一些事)等
总结一下DubboConfigConfigurationRegistrar做的事
- 往Spring容器中放一个DubboConfigConfiguration.Single�类,
Single就是一个配置模版类,上面会有@EnableConfigurationBeanBindings,这个类又会注入一个类ConfigurationBeanBindingsRegister�,这个类实现了ImportBeanDefinitionRegistrar�, 会在Spring启动的时候调用到它的registerBeanDefinitions,这个方法最终完成了将Single上面这些个配置
转换成XXXConfig, 同时还注册了一个ConfigurationBeanBindingPostProcessor,完成从配置文件读取这一个个配置,并放到XXXConfig中
- 如果配置的multiple=true,就会多走一套multiple配置逻辑,核心逻辑和Single其实差不错
- registerCommonBeans:注册了很多Dubbo中用到的通用bean
�
@DubboComponentScan
�这里同样引入了一个DubboComponentScanRegistrar,也就是在spring启动的过程中会调用到它的registerBeanDefinitions方法
getPackagesToScan: 返回一个需要扫描的路径集合
registerServiceAnnotationBeanPostProcessor(packagesToScan, registry): 又往spring容器中注册了一个ServiceAnnotationBeanPostProcessor类,通过传入了刚刚获取的需要扫描的路径集合
ServiceAnnotationBeanPostProcessor
ServiceAnnotationBeanPostProcessor继承的是ServcieClassPostProcessor
这里做了几件事:
- 注册DubboBootstrapApplicationListener到spring容器中,这个listener负责监听ContextRefreshedEvent事件
- 如果packagesToScan中有占位符,替换掉,然后使用registerServiceBeans, 将@DubboServcie扫描到Spring容器中
ServiceClassPostProcessor.registerServiceBeans(packagesToScan, BeanDefinitionRegistry registry)
总结一下:
- 创建了一个DubboClassPathBeanDefinitionScanner专门用来扫指定路径包下面的beanDefinition的
- 给scanner设置了IncludeFilter,就是我只扫@DubboService、@Service这些注解的
- 遍历路径包,然后使用scanner.scan来看路径包下面有@DubboService、@Service的beanDefinition
- 将扫到的BeanDefinition,然后调用registerServiceBean 将这些spring的bean处理成Dubbo自己的bean
ServiceClassPostProcessor.registerServiceBean()
�
Consumer#Application
Consumer的大体逻辑和Provider一样,肯定也有读取配置的地方,最重要的地方就是@DubboReference是如何解析的
入口
@EnableDubboConfig -> @Import(DubboConfigConfigurationRegistrar.class) ->DubboConfigConfigurationRegistrar�.registerBeanDefinitions ->registerCommonBeans(registry);
ReferenceAnnotationBeanPostProcessor
这个类其实挺有意思的,它继承了AbstractAnnotationBeanPostProcessor,
这个AbstractAnnotationBeanPostProcessor又继承了InstantiationAwareBeanPostProcessorAdapter�,我们来看一下它的实现类有哪些?
是不是非常的熟悉,AutoWiredAnnotationBeanPostProcessor,其实@ReferencService和@AutoWired的实现原理是一样的,都是在Spring依赖注入的时候,需要做属性的注入,最终会调用到ReferenceAnnotationBeanPostProcessor.doGetInjectedBean
ReferenceAnnotationBeanPostProcessor.doGetInjectedBean
一共做了几件事:
- buildReferencedBeanName�
使用buildReferencedBeanName方法得到一个bean的名字,这个bean是需要引入服务的beanName,最终生成的格式为ServiceBean:org.apache.dubbo.demo.DemoService,如果说设置了group,或者version,都会拼接上去,最终生成的格式为:ServiceBean:interfaceClassName:version:group
- getReferenceBeanName�
getReferencBeanName得到的referenceBeanName是我引入时产生的服务名,而referencedBeanName对应的serviceBean的服务名称,从generateReferenceBeanName我们看的出来,referenceBeanName产生规则为:@Reference注解所有属性的值进行拼接,所以就是@Reference引入的接口是同一个,但注解的值不一样的话,这个名字也是不一样的。那么这个名字有什么用,他的作用是放到缓存中作为key
- buildReferenceBeanIfAbsent�
代码主要就是判断缓存中是否存在,如果不存在就进行创建流程,这里比较复杂,我们关系最终得到的referenceBean:
- isLocalServiceBean�
判断是否是本地服务
- registerReferenceBean�
�
�