1. Dubbo启动时的架构和流程
启动原理图
如上图所示,Dubbo在Spring的启动主要分为以下三大步:
- 根据配置文件生成对应的
XxxConfig
对象,例如ApplicationConfig、ProtocolConfig
等等 - 扫描并处理
@Service
注解,生成Spring
中的Bean
对象和Dubbo
中的ServiceBean
对象,每个@Service
注解都会生成以上两种对象,其中XxxConfig
对象作为ServiceBean
对象的属性被赋值
Dubbo在Spring中的启动类:
public class Application {
// 启动
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
context.start();
System.in.read();
}
// 配置
@Configuration
@EnableDubbo(scanBasePackages = "org.apache.dubbo.demo.provider")
@PropertySource("classpath:/spring/dubbo-provider.properties")
static class ProviderConfiguration {
}
}
从上述代码中可以看到,配置配上有两个重要的注解:
- @PropertySource:这个注解是Spring的注解,结合spring源码,我们知道
@PropertySource
表示将classpath:/spring/dubbo-provider.properties
文件中的配置项添加到Environment
对象中,Dubbo可以很方便的从Environment
中取出自己需要的配置信息,并生成各种xxxConfig
对象,如上面启动原理图最左边一栏所示! - @EnableDubbo:表示对指定
org.apache.dubbo.demo.provider
包下的类进行扫描,扫描@Service
与@Reference
注解,并且进行处理
其中@EnableDubbo
是Dubbo的核心注解,他里边又包含了两个注解,这两个注解实现了Dubbo最主要的功能!
- @EnableDubboConfig:作用是把
classpath:/spring/dubbo-provider.properties
配置文件中的配置组装成各种xxxConfig
对象。 - @DubboComponentScan:扫描
@Service
与@Reference
注解,并进行处理
下面就@EnableDubboConfig、@DubboComponentScan
这两个注解所完成的功能做具体的源码级分析!
2. 扫描配置生成XxxConfig对象
上文讲到扫描配置生成XxxConfig
对象是由@EnableDubboConfig
来做的,在这个注解内部,通过@Import(DubboConfigConfigurationRegistrar.class)
向容器中导入了一个DubboConfigConfigurationRegistrar
类,这个类实现了ImportBeanDefinitionRegistrar
接口,意味着在其重写的registerBeanDefinitions()
方法中会通过注册BeanDefination
的形式向Spring中注册新的Bean,跟进源码看一下DubboConfigConfigurationRegistrar
的实现:
可以看到在其重写的registerBeanDefinitions()
方法中,注册了两个Bean定义:
DubboConfigConfiguration.Single.class
:单配置XxxConfig类
// @EnableDubboConfigBindings注解内部也有一个@Import注解,
//导入的是DubboConfigBindingsRegistrar.class。
//该类会获取@EnableDubboConfigBindings注解中的value,也就是多个@EnableDubboConfigBinding注解,
//然后利用DubboConfigBindingRegistrar去处理这些@EnableDubboConfigBinding注解,生成对应的Bean定义
@EnableDubboConfigBindings({
@EnableDubboConfigBinding(prefix = "dubbo.application", type = ApplicationConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.module", type = ModuleConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.registry", type = RegistryConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.protocol", type = ProtocolConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.monitor", type = MonitorConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.provider", type = ProviderConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.consumer", type = ConsumerConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.config-center", type = ConfigCenterBean.class),
@EnableDubboConfigBinding(prefix = "dubbo.metadata-report", type = MetadataReportConfig.class),
@EnableDubboConfigBinding(prefix = "dubbo.metrics", type = MetricsConfig.class)
})
public static class Single {
}
DubboConfigConfiguration.Multiple.class
:多配置XxxConfig类
@EnableDubboConfigBindings({
@EnableDubboConfigBinding(prefix = "dubbo.applications", type = ApplicationConfig.class, multiple = true),
@EnableDubboConfigBinding(prefix = "dubbo.modules", type = ModuleConfig.class, multiple = true),
@EnableDubboConfigBinding(prefix = "dubbo.registries", type = RegistryConfig.class, multiple = true),
@EnableDubboConfigBinding(prefix = "dubbo.protocols", type = ProtocolConfig.class, multiple = true),
@EnableDubboConfigBinding(prefix = "dubbo.monitors", type = MonitorConfig.class, multiple = true),
@EnableDubboConfigBinding(prefix = "dubbo.providers", type = ProviderConfig.class, multiple = true),
@EnableDubboConfigBinding(prefix = "dubbo.consumers", type = ConsumerConfig.class, multiple = true),
@EnableDubboConfigBinding(prefix = "dubbo.config-centers", type = ConfigCenterBean.class, multiple = true),
@EnableDubboConfigBinding(prefix = "dubbo.metadata-reports", type = MetadataReportConfig.class, multiple = true),
@EnableDubboConfigBinding(prefix = "dubbo.metricses", type = MetricsConfig.class, multiple = true)
})
public static class Multiple {
}
单配置类和多配置类的区别是什么?
从上面的prefix = "dubbo.application"
与prefix = "dubbo.applications"
不难看出,单配置与多配置XxxConfig的区别,比如以下配置
# 应用名配置
dubbo.application.name=dubbo-demo-provider1-application
# 协议配置
dubbo.protocols.p1.name=tdubbo
dubbo.protocols.p1.port=20880
dubbo.protocols.p1.host=0.0.0.0
dubbo.protocols.p2.name=dubbo
dubbo.protocols.p2.port=20881
dubbo.protocols.p2.host=0.0.0.0
- 单配置XxxConfig:代表配置文件中针对某项配置只有一种,比如上文
dubbo.application.name
只有一种配置,就会走源码中的DubboConfigConfiguration.Single.class
, 生成ApplicationConfig
类,name
会作为ApplicationConfig
类的属性被赋值为dubbo-demo-provider1-application
- 多配置XxxConfig:代表配置文件中针对某项配置有多种,会生成多个XxxConfig类。比如上文的
protocols
协议相关配置,就会走源码中的DubboConfigConfiguration.Multiple.class
,分别生成protocolP1Config
、protocolP2Config
多个类,name、port、host
作为属性填充到对应的类中
生成XxxConfig的是由DubboConfigBindingRegistrar
来处理的,内部代码如下:
private void registerDubboConfigBeans(String prefix,
Class<? extends AbstractConfig> configClass,
boolean multiple,
BeanDefinitionRegistry registry) {
// 从properties文件中根据前缀拿对应的配置项,比如根据dubbo.application前缀,
// 就可以拿到:
// dubbo.application.name=dubbo-demo-provider-application
Map<String, Object> properties = getSubProperties(environment.getPropertySources(), prefix);
// 如果没有相关的配置项,则不需要注册BeanDefinition
if (CollectionUtils.isEmpty(properties)) {
if (log.isDebugEnabled()) {
log.debug("There is no property for binding to dubbo config class [" + configClass.getName()
+ "] within prefix [" + prefix + "]");
}
return;
}
// 根据配置项生成beanNames,为什么会有多个?
// 普通情况一个dubbo.application前缀对应一个ApplicationConfig类型的Bean
// 特殊情况下,比如dubbo.protocols对应了:
// 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
// 那么就需要对应两个ProtocolConfig类型的Bean,那么就需要两个beanName:p1和p2
// 这里就是multiple为true或false的区别,名字的区别,根据multiple用来判断是否从配置项中获取beanName
// 如果multiple为false,则看有没有配置id属性,如果没有配置则自动生成一个beanName.
Set<String> beanNames = multiple ? resolveMultipleBeanNames(properties) :
Collections.singleton(resolveSingleBeanName(properties, configClass, registry));
for (String beanName : beanNames) {
// 为每个beanName,注册一个空的BeanDefinition
registerDubboConfigBean(beanName, configClass, registry);
// 为每个bean注册一个DubboConfigBindingBeanPostProcessor的Bean后置处理器
registerDubboConfigBindingBeanPostProcessor(prefix, beanName, multiple, registry);
}
// 注册一个NamePropertyDefaultValueDubboConfigBeanCustomizer的bean
registerDubboConfigBeanCustomizers(registry);
}
3. Dubbo如何处理@Service
Dubbo通过@EnableDubbo(scanBasePackages = "org.apache.dubbo.demo.provider")
扫描到对应路径下的@Service
注解 ,主要做了两件事情,得到两个Bean
- 扫描
@Service
标注的类,得到一个BeanDefinition
,一个Spring中的Bean - 在扫描完了之后,会针对所得到的每个
BeanDefinition
,都会额外的再生成一个ServiceBean
类型的Bean对象。这个ServiceBean
通过Ref
属性与Spring中的Bean联系起来!
ServiceBean
ServiceBean表示一个Dubbo服务,ServiceBean对象中的参数就表示服务的参数,比如timeout,该对象的参数值来至@Service注解中所定义的。还有其他参数,比如:
ref
,表示服务的具体实现类interface
,表示服务的接口parameters
,表示服务的参数(@Service注解中所配置的信息)application
,表示服务所属的应用protocols
,表示服务所使用的协议registries
,表示服务所要注册的注册中心
暴露服务
ServiceBean
还实现了ApplicationListener<ContextRefreshedEvent>
,意味着在spring容器启动完毕后,会调用onApplicationEvent
方法向注册中心暴露服务!点击查看服务导出逻辑
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 当前服务没有被导出并且没有卸载,才导出服务
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
// 服务导出(服务注册)
export();
}
}
①:Dubbo为什么要多生成一个ServiceBean?
因为对于Dubbo来说@Service标注的对象是一个服务,并且,还需要解析@Service注解的配置信息例如:@Service(version = "timeout", timeout = 4000)
,因为这些都是服务的参数信息,所以需要一个额外的ServiceBean
来存储这些信息,另外暴露服务也需要ServiceBean
②:扫描@Service源码解析
@Service
的扫描是由@DubboComponentScan
中通过@Import
导入的DubboComponentScanRegistrar
来处理的
其中DubboComponentScanRegistrar
的registerBeanDefinitions()
中分别处理的@Service和@Reference注解,如下代码所示:
public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
System.out.println("执行DubboComponentScanRegistrar");
// 拿到DubboComponentScan注解所定义的包路径,扫描该package下的类,识别这些类上
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
// 处理@Service注解
// 实现了BeanDefinitionRegistryPostProcessor接口,所以在Spring启动时会调用postProcessBeanDefinitionRegistry方法
// 该方法会进行扫描,扫描@Service注解了的类,然后生成BeanDefinition(会生成两个,一个普通的bean,一个ServiceBean),后续的Spring周期中会生成Bean
// 在ServiceBean中会监听ContextRefreshedEvent事件,一旦Spring启动完后,就会进行服务导出
registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
// 处理@Reference注解
// 实现了AnnotationInjectedBeanPostProcessor接口,继而实现了InstantiationAwareBeanPostProcessorAdapter接口
// 所以Spring在启动时,在对属性进行注入时会调用AnnotationInjectedBeanPostProcessor接口中的postProcessPropertyValues方法
// 在这个过程中会按照@Refrence注解的信息去生成一个RefrenceBean对象
registerReferenceAnnotationBeanPostProcessor(registry);
}
③:如何处理@Service注解的?
进入registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
这个
ServiceAnnotationBeanPostProcessor
是一个Bean工厂的后置处理器,可以用来生成Bean定义,而bean的后置处理器没有这个功能,注意一下区别!,既然ServiceAnnotationBeanPostProcessor
实现了BeanFactoryPostProcessor
那么在Spring容器启动时就会回调其postProcessBeanDefinitionRegistry
方法!
回调postProcessBeanDefinitionRegistry
方法代码如下:
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);
if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
// 扫描包,进行Bean注册
registerServiceBeans(resolvedPackagesToScan, registry);
} else {
if (logger.isWarnEnabled()) {
logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
}
}
}
registerServiceBeans(resolvedPackagesToScan, registry)
中会进行对@Service的扫描:
此处你可能会有疑问
④:@Service注解不是Dubbo包下的吗?为什么会被扫描到?
我们知道,Spring
可以扫描@Component、@Service
等注解,并把它们生成bean对象。这其中有一个扫描过滤器DefaultFilters
,过滤器要求只扫描@Component、@Service
等注解。此时Dubbo
中的@Service
是不能被Spring
扫描到的,因为它不是Spring包下的@Service
,但是Dubbo又想使用Spring的包扫描逻辑,那么Dubbo是怎么做的呢?
Dubbo没有使用Spring自带的扫描器,而是自定义了自己的扫描器DubboClassPathBeanDefinitionScanner
,自定义扫描器相对于Spring的ClassPathBeanDefinitionScanner
并没有做太多的改变,只是把useDefaultFilters
设置为了false
,表示不使用Spring
默认的过滤逻辑,并添加新的过滤逻辑:只扫描Dubbo包下的@Service
注解,这样既利用了Spring的包扫描逻辑,又自定义了扫描逻辑,这一点算是Dubbo对Spring框架的一点扩展!
最后注册ServiceBean
的BeanDefination
,然后使用Spring
的逻辑生成ServiceBean
实例
⑤:ServiceBean的属性赋值
SerivceBean对象的属性主要是@Service
注解中标注的一些值例如:@Service(version = "timeout", timeout = 4000)
,赋值过程通过修改BeanDefination
的方式设置对应属性,这些值分为两种:
- 简单值:例如
@Service(version = "timeout")
这些简单值就直接赋值即可 - 复杂值:例如
"ref"、"interface","parameters"、"provider"
等等,采用addPropertyReference
等方式赋值
4. Dubbo如何处理@Reference
@Component("demoServiceComponent")
public class DemoServiceComponent implements DemoService {
//引用demoService服务
@Reference
private DemoService demoService;
}
上面介绍了通过@Service的作用是生成两个bean,并暴露服务,那么在消费端通常是使用@Reference
引入服务的,下面来看一下Dubbo的底层是怎么处理@Reference
注解的!
和处理@Service
注解一样,Dubbo在处理@Reference
也是由@DubboComponentScan
中的DubboComponentScanRegistrar类中的registerBeanDefinitions()
方法来搞定的。这个方法内部分别对@Service
和@Reference
做了处理
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
System.out.println("执行DubboComponentScanRegistrar");
// 拿到DubboComponentScan注解所定义的包路径,扫描该package下的类,识别这些类上
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
//处理 @Service
registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
//处理 @Reference
registerReferenceAnnotationBeanPostProcessor(registry);
}
进入处理@Reference的registerReferenceAnnotationBeanPostProcessor
方法中:
可以看到它又为我们注册了一个类ReferenceAnnotationBeanPostProcessor
,那么这个类的作用是什么呢?
ReferenceAnnotationBeanPostProcessor
是处理@Reference
注解的ReferenceAnnotationBeanPostProcessor
的顶层父类是一个BeanPostProcessor
,用来对Bean进行扩展修改的,这点不同于BeanFactoryPostProcessor
,BeanFactoryPostProcessor
是修改或增加Bean定义的
4.1 寻找注入点
由于注入的ReferenceAnnotationBeanPostProcessor
类是一个Bean的后置处理器,所以进入postProcessPropertyValues
方法中查看对@Component
标注的类的扩展,首先要寻找注入点:找到@Reference
注解标注在哪个位置上!
寻找注入点会查看属性和方法上是否标有@Reference
注解!被@Reference
注解的属性或方法都是注入点。
4.1 注入点赋值
针对某个Bean找到所有注入点之后,就会进行注入了,注入就是给属性或给set方法赋值。此时就会调用ReferenceAnnotationBeanPostProcessor
的doGetInjectedBean()
方法来得到一个对象ReferenceBean
ReferenceBean
对于Dubbo来说,使用@Reference
不再是简单的从容器拿到对象赋值就行了,应具有Dubbo特有的一些功能,所以在赋值过程中需要一些针对Dubbo的特殊处理。Dubbo通过生成一个复杂对象ReferenceBean
来实现, ReferenceBean
就类似于扫描@Serviuce
时生成的ServiceBean
。包含以下几个主要属性
protected void configureBean(C configBean) throws Exception {
// 把@Reference注解中的配置项赋值给configBean
preConfigureBean(attributes, configBean);
// 设置注册中心
configureRegistryConfigs(configBean);
// 设置监控中心
configureMonitorConfig(configBean);
// 应用名配置
configureApplicationConfig(configBean);
// ModuleConfig
configureModuleConfig(configBean);
// 设置applicationContext、interfaceName、consumer、methods属性,并调用ReferenceBean对象的afterPropertiesSet方法
postConfigureBean(attributes, configBean);
}
此外,ReferenceBean
还实现了FactoryBean
这样,通过实现FactoryBean
接口,并重写getObject()
即可获取注入点赋值的具体对象!
// FactoryBean的 getObject 接口:获取某个对象
@Override
public Object getObject() {
return get();
}
// Dubbo创建的代理对象
ref = createProxy(map);
public synchronized T get() {
checkAndUpdateSubConfigs();
if (destroyed) {
throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
}
if (ref == null) {
// 入口
init();
}
return ref; // dubbo生成的代理对象,也就是注入点的代理对象
}
下面来进入doGetInjectedBean()
方法,来看一下注入点赋值的具体过程
// 该方法得到的对象会赋值给@ReferenceBean注解的属性
@Override
protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
InjectionMetadata.InjectedElement injectedElement) throws Exception {
// 得到当前所引入服务对应的ServiceBean的beanName,也就是referencedBeanName
String referencedBeanName = buildReferencedBeanName(attributes, injectedType);
// @Reference(methods=[Lorg.apache.dubbo.config.annotation.Method;@39b43d60) org.apache.dubbo.demo.DemoService
// 根据@Reference注解的所有信息+属性接口类型得到一个referenceBeanName
String referenceBeanName = getReferenceBeanName(attributes, injectedType);
// 生成一个ReferenceBean对象
ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);
// 把referenceBean添加到Spring容器中去
registerReferenceBean(referencedBeanName, referenceBean, attributes, injectedType);
//放缓存
cacheInjectedReferenceBean(referenceBean, injectedElement);
// 创建一个代理对象,Service中的属性被注入的就是这个代理对象
// 内部会调用referenceBean.get();
return getOrCreateProxy(referencedBeanName, referenceBeanName, referenceBean, injectedType);
}
根据以上源码,总结得出@Reference
注解服务引入的过程是这样的:
- 得到当前所引入服务对应的
ServiceBean
的beanName
(源码中叫referencedBeanName
) - 根据
@Reference
注解的所有信息+属性接口类型得到一个referenceBeanName
- 如果
referenceBeanCache
没有ReferenceBean
对象,则创建一个ReferenceBean
,有则获取 - 根据
referencedBeanName
(ServiceBean的beanName)判断Spring容器中是否存在该bean
①:如果存在,则给ReferenceBean
的ref
属性(代理对象)取一个别名,别名为referenceBeanName
。
②:如果不存在 ,则将创建出来的ReferenceBean
注册到Spring容器中,由于ReferenceBean
是一个FactoryBean
,后续可以通过getObject()
方法获取到ref代理对象 - 通过
referenceBean.get()
方法返回一个ref
代理对象,作为注入点赋值对象!
问题:通过@Autowired注入可以使用某个服务吗?
@Component("demoServiceComponent")
public class DemoServiceComponent implements DemoService {
@Reference
private DemoService demoService;
//使用 @Autowired 注入的 demoService1 可以使用吗?
@Autowired
private DemoService demoService1;
@Override
public String sayHello(String name) {
return demoService.sayHello(name); // Invoker
}
}
答案:可以,但前提条件是在别的地方使用过@Reference
注入了DemoService
接口服务!
因为
- Dubbo在扫描到
@Reference
后,回向容器中注入一个ReferenceBean
ReferenceBean
又实现了FactoryBean
接口,可通过getObject
方法向容器中注入DemoService
代理对象- 而且
@Reference
的bean的后置处理器是优先于@Autowired
被加载的
此时,@Autowired
从容器中获取DemoService
代理对象时,容器中已有该代理对象存在,所以可以使用@Autowired
注入某个服务!