8. Dubbo与Spring整合 - 配置解析
1. 开篇
本文主要介绍Dubbo
与Spring
进行整合、Dubbo中propertie
文件解析以及处理原理。
虽然Dubbo
可以不和Spring
一起使用,但在我们的实际项目中,Dubbo
是经常和Spring
系列框架一起使用的。
在使用Dubbo时,我们经常使用的两个注解@DubboReference
、@DubboService
,Dubbo与Spring整合时,涉及的主要问题是如何解析这两个注解。
那么解析过程,我们可以想到的是:
- 解析配置文件
- 解析Dubbo的扫描路径
- 扫描到
@DubboService
注解生成对应的Bean对象 - 扫描到
@DubboReference
注解进行依赖注入 - 启动
Dubbo
服务对外暴露服务
2. 示例
先来一个简单的示例,先启动zookeeper
,启动示例,一个简单的Dubbo服务就已经提供服务了。
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 {
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
return registryConfig;
}
}
}
dubbo-provider.properties
文件内容:
dubbo.application.name=dubbo-demo-annotation-provider
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
ProviderConfiguration
是一个配置类,在服务启动时,会读取这个配置类。
@PropertySource
是spring的注解,负责解析配置文件,把解析到的内容放到Environment
类的对象里。而Dubbo则是从这个对象里,拿到配置值,生成对应的对象。 比如,dubbo.application.name
会生成一个ApplicationConfig
对象,而dubbo.protocol.*
则会生成一个ProtocolConfig
对象。
@EnableDubbo
表示启动dubbo服务,定义了扫描dubbo服务的路径。
3. 源码分析
3.1 启用Dubbo
刚才说的处理Dubbo配置文件、处理@DubboReference
和@DubboService
注解,都是由@EnableDubbo
注解触发的。
在@EnableDubbo
注解上有两个其他Dubbo
注解:
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
}
@EnableDubboConfig
注解主要是处理dubbo的配置文件,把配置文件的内容处理解析成一个一个的配置对象。
@DubboComponentScan
注解主要是负责扫描处理@DubboReference
和@DubboService
注解,把对应的类生成Bean对象或进行依赖注入。
3.2 开启Dubbo配置
@Import(DubboConfigConfigurationRegistrar.class)
public @interface EnableDubboConfig {
boolean multiple() default true;
}
EnableDubboConfig
通过Spring的Import
注解把DubboConfigConfigurationRegistrar
进行导入,Spring在导入的时候会进行判断,判断导入的类是不是ImportBeanDefinitionRegistrar
,如果是的话,则会调用类类里面的registerBeanDefinitions
方法。
先拿到EnableDubboConfig
注解里multiple
属性值,该值默认为true
。
调用registerBeans
方法往Spring容器里注册Bean。可以看到multiple
为true
会再调一次registerBeans
方法,只不过参数不同。一个是DubboConfigConfiguration.Single.class
,另一个是DubboConfigConfiguration.Multiple.class
。这两个类很像,其中prefix
值就是我们在配置文件里写的前缀,type
为解析成配置类的类型,让dubbo的配置与Dubbo的配置类进行一一对应。
3.3 解析配置绑定关系
DubboConfigConfiguration.Single
和DubboConfigConfiguration.Multiple
的类上面有EnableConfigurationBeanBindings
注解,Spring也会对其进行解析。 EnableConfigurationBeanBindings
注解上又导入了ConfigurationBeanBindingsRegister
类,而ConfigurationBeanBindingsRegister
也实现了ImportBeanDefinitionRegistrar
,所以在spring解析的时候,会调用他的registerBeanDefinitions
方法。
@Import({ConfigurationBeanBindingsRegister.class})
public @interface EnableConfigurationBeanBindings {
EnableConfigurationBeanBinding[] value();
}
来看一下ConfigurationBeanBindingsRegister
类的registerBeanDefinitions
方法。
首先解析EnableConfigurationBeanBindings
注解,后去value
属性值,value
属性值为一个一个的EnableConfigurationBeanBinding
注解对应的值。
registrar.setEnvironment(environment)
是为了方便从environment
对象里获取值,根据EnableConfigurationBeanBinding
注解的prefix
,到environment
对象里查找对应的值。
循环遍历annotationAttributes
,按照EnableConfigurationBeanBinding
定义的映射关系,把配置注册成BeanDefinition
。
3.4 注册配置BeanDefinition
接下来调用registerConfigurationBeanDefinitions
方法,注册BeanDefinition
。
首先获取注解的prefix
属性值,如果该属性值有包含占位符的话,就从environment
对象里查找占位符并进行替换。
获取注解的value
属性值,该值是一个class对象,例如dubbo.application
对应的配置类为org.apache.dubbo.config.ApplicationConfig
。
接着调用registerConfigurationBeans
方法注册Bean。
3.5 注册配置Bean
调用getSubProperties
方法获取配置文件里的值。 例如,下面这个配置,会被解析为:
ini复制代码#application
name=dubbo-demo-annotation-provider
#protocol
name=dubbo
port=20880
接下来为配置生成对应的beanNames
。为什么是复数呢?因为如果multiple
是true
的话,我们可能会配置多个配置,比如配置多个协议,例如:
ini复制代码dubbo.protocol.first.name=dubbo
dubbo.protocol.first.port=20880
dubbo.protocol.second.name=http
dubbo.protocol.second.port=20881
解析后,注册逻辑是一样的,所以如果是单个的话,会把单个进行包一层,同一层Set
集合。
3.6 解析多个配置
private Set<String> resolveMultipleBeanNames(Map<String, Object> properties) {
Set<String> beanNames = new LinkedHashSet<String>();
for (String propertyName : properties.keySet()) {
int index = propertyName.indexOf(".");
if (index > 0) {
String beanName = propertyName.substring(0, index);
beanNames.add(beanName);
}
}
return beanNames;
}
如果是复数形式,生成beanName逻辑也挺简单,把key按小数点.
分隔,小数点前面作为bean的名字。
例如,dubbo.protocol.first.name=dubbo
,propertyName
为first.name
, beanName
为first
。
3.7 解析单个服务
private String resolveSingleBeanName(Map<String, Object> properties, Class<?> configClass,
BeanDefinitionRegistry registry) {
String beanName = (String) properties.get("id");
if (!StringUtils.hasText(beanName)) {
BeanDefinitionBuilder builder = rootBeanDefinition(configClass);
beanName = BeanDefinitionReaderUtils.generateBeanName(builder.getRawBeanDefinition(), registry);
}
return beanName;
}
如果是单数形式,先把class对象生成BeanDefinition
,beanName
是根据id
属性来的,如果没有指定的话,spring会根据类名以及hashcode生成一个beanName
,例如org.apache.dubbo.config.ApplicationConfig#0
。
3.8 注册配置BeanDefinition
拿到beanName
、class对象
后就可以生成BeanDefinition
以及注册BeanDefinition
了。
先根据class生成BeanDefinition
,设置source
为EnableConfigurationBeanBinding.class
,处理属性值,初始化Metadata
属性值,最后注册成BeanDefinition
。
3.9 注册BeanPostProcessor
生成BeanDefinition
后,但是里面的属性都是空的。我们知道,在spring中,给Bean属性赋值的过程是在BeanPostProcessor
中完成的。
所以,Dubbo生成BeanDefinition
之后,还需要一个BeanPostProcessor
来为其属性赋值。
调用registerInfrastructureBean
方法注册ConfigurationBeanBindingPostProcessor
。
下面来看下BeanPostProcessor
赋值的过程,赋值过程是在postProcessBeforeInitialization
方法。
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
BeanDefinition beanDefinition = getNullableBeanDefinition(beanName);
if (isConfigurationBean(bean, beanDefinition)) {
bindConfigurationBean(bean, beanDefinition);
customize(beanName, bean);
}
return bean;
}
主体逻辑很简单,先从BeanFactory
里获取BeanDefinition
,判断是否为Dubbo的配置Bean,再进行赋值操作。
是否为Dubbo的配置Bean的判断逻辑主要为判断BeanDefinition
的source
是否为EnableConfigurationBeanBinding.class
。
3.10 属性绑定
此方法进行属性的绑定。
private void bindConfigurationBean(Object configurationBean, BeanDefinition beanDefinition) {
Map<String, Object> configurationProperties = getConfigurationProperties(beanDefinition);
boolean ignoreUnknownFields = getIgnoreUnknownFields(beanDefinition);
boolean ignoreInvalidFields = getIgnoreInvalidFields(beanDefinition);
getConfigurationBeanBinder().bind(configurationProperties, ignoreUnknownFields, ignoreInvalidFields, configurationBean);
if (log.isInfoEnabled()) {
log.info("The configuration bean [" + configurationBean + "] have been binding by the " +
"configuration properties [" + configurationProperties + "]");
}
}
先获取到要进行绑定的配置,即前面生成的name:dubbo-demo-annotation-provider
、name:dubbo
等。
获取BeanBinder
对象,调用DataBinder
的bind方法进行属性的赋值操作。
赋值操作的简单过程为,以name:dubbo-demo-annotation-provider
为例,从configurationProperties
对象里获取name
值,也就是dubbo-demo-annotation-provider
,再从类里面找到name
属性,通过反射把dubbo-demo-annotation-provider
赋值给ApplicationConfig
对象。
4. 后记
关于Dubbo启动解析配置文件,并生成对应的Bean对象到这里接结束了。