开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情
开篇
本文主要介绍Dubbo与Spring进行整合、Dubbo中propertie文件解析以及处理原理。
虽然Dubbo可以不和Spring一起使用,但在我们的实际项目中,Dubbo是经常和Spring系列框架一起使用的。
在使用Dubbo时,我们经常使用的两个注解@DubboReference、@DubboService,Dubbo与Spring整合时,涉及的主要问题是如何解析这两个注解。
那么解析过程,我们可以想到的是:
- 解析配置文件
- 解析Dubbo的扫描路径
- 扫描到
@DubboService注解生成对应的Bean对象 - 扫描到
@DubboReference注解进行依赖注入 - 启动
Dubbo服务对外暴露服务
示例
先来一个简单的示例,先启动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服务的路径。
源码分析
启用Dubbo
刚才说的处理Dubbo配置文件、处理@DubboReference和@DubboService注解,都是由@EnableDubbo注解触发的。
在@EnableDubbo注解上有两个其他Dubbo注解:
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
}
@EnableDubboConfig注解主要是处理dubbo的配置文件,把配置文件的内容处理解析成一个一个的配置对象。
@DubboComponentScan注解主要是负责扫描处理@DubboReference和@DubboService注解,把对应的类生成Bean对象或进行依赖注入。
开启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的配置类进行一一对应。
解析配置绑定关系
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。
注册配置BeanDefinition
接下来调用registerConfigurationBeanDefinitions方法,注册BeanDefinition。
首先获取注解的prefix属性值,如果该属性值有包含占位符的话,就从environment对象里查找占位符并进行替换。
获取注解的value属性值,该值是一个class对象,例如dubbo.application对应的配置类为org.apache.dubbo.config.ApplicationConfig。
接着调用registerConfigurationBeans方法注册Bean。
注册配置Bean
调用getSubProperties方法获取配置文件里的值。
例如,下面这个配置,会被解析为:
#application
name=dubbo-demo-annotation-provider
#protocol
name=dubbo
port=20880
接下来为配置生成对应的beanNames。为什么是复数呢?因为如果multiple是true的话,我们可能会配置多个配置,比如配置多个协议,例如:
dubbo.protocol.first.name=dubbo
dubbo.protocol.first.port=20880
dubbo.protocol.second.name=http
dubbo.protocol.second.port=20881
解析后,注册逻辑是一样的,所以如果是单个的话,会把单个进行包一层,同一层Set集合。
解析多个配置
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。
解析单个服务
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。
注册配置BeanDefinition
拿到beanName、class对象后就可以生成BeanDefinition以及注册BeanDefinition了。
先根据class生成BeanDefinition,设置source为EnableConfigurationBeanBinding.class,处理属性值,初始化Metadata属性值,最后注册成BeanDefinition。
注册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。
属性绑定
此方法进行属性的绑定。
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对象。
后记
关于Dubbo启动解析配置文件,并生成对应的Bean对象到这里接结束了。