Dubbo源码|八、Dubbo与Spring整合原理—配置解析

904 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情

开篇

本文主要介绍DubboSpring进行整合、Dubbo中propertie文件解析以及处理原理。

虽然Dubbo可以不和Spring一起使用,但在我们的实际项目中,Dubbo是经常和Spring系列框架一起使用的。

在使用Dubbo时,我们经常使用的两个注解@DubboReference@DubboService,Dubbo与Spring整合时,涉及的主要问题是如何解析这两个注解。

那么解析过程,我们可以想到的是:

  1. 解析配置文件
  2. 解析Dubbo的扫描路径
  3. 扫描到@DubboService注解生成对应的Bean对象
  4. 扫描到@DubboReference注解进行依赖注入
  5. 启动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方法。

image.png

先拿到EnableDubboConfig注解里multiple属性值,该值默认为true

调用registerBeans方法往Spring容器里注册Bean。可以看到multipletrue会再调一次registerBeans方法,只不过参数不同。一个是DubboConfigConfiguration.Single.class,另一个是DubboConfigConfiguration.Multiple.class。这两个类很像,其中prefix值就是我们在配置文件里写的前缀,type为解析成配置类的类型,让dubbo的配置与Dubbo的配置类进行一一对应。

解析配置绑定关系

image.png

DubboConfigConfiguration.SingleDubboConfigConfiguration.Multiple的类上面有EnableConfigurationBeanBindings注解,Spring也会对其进行解析。 EnableConfigurationBeanBindings注解上又导入了ConfigurationBeanBindingsRegister类,而ConfigurationBeanBindingsRegister也实现了ImportBeanDefinitionRegistrar,所以在spring解析的时候,会调用他的registerBeanDefinitions方法。

@Import({ConfigurationBeanBindingsRegister.class})
public @interface EnableConfigurationBeanBindings {
    EnableConfigurationBeanBinding[] value();
}

来看一下ConfigurationBeanBindingsRegister类的registerBeanDefinitions方法。

image.png

首先解析EnableConfigurationBeanBindings注解,后去value属性值,value属性值为一个一个的EnableConfigurationBeanBinding注解对应的值。

registrar.setEnvironment(environment)是为了方便从environment对象里获取值,根据EnableConfigurationBeanBinding注解的prefix,到environment对象里查找对应的值。

循环遍历annotationAttributes,按照EnableConfigurationBeanBinding定义的映射关系,把配置注册成BeanDefinition

注册配置BeanDefinition

接下来调用registerConfigurationBeanDefinitions方法,注册BeanDefinitionimage.png

首先获取注解的prefix属性值,如果该属性值有包含占位符的话,就从environment对象里查找占位符并进行替换。

获取注解的value属性值,该值是一个class对象,例如dubbo.application对应的配置类为org.apache.dubbo.config.ApplicationConfig

接着调用registerConfigurationBeans方法注册Bean。

注册配置Bean

image.png

调用getSubProperties方法获取配置文件里的值。 例如,下面这个配置,会被解析为:

#application
name=dubbo-demo-annotation-provider
#protocol
name=dubbo
port=20880

接下来为配置生成对应的beanNames。为什么是复数呢?因为如果multipletrue的话,我们可能会配置多个配置,比如配置多个协议,例如:

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=dubbopropertyNamefirst.namebeanNamefirst

解析单个服务

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对象生成BeanDefinitionbeanName是根据id属性来的,如果没有指定的话,spring会根据类名以及hashcode生成一个beanName,例如org.apache.dubbo.config.ApplicationConfig#0

注册配置BeanDefinition

拿到beanNameclass对象后就可以生成BeanDefinition以及注册BeanDefinition了。

image.png

先根据class生成BeanDefinition,设置sourceEnableConfigurationBeanBinding.class,处理属性值,初始化Metadata属性值,最后注册成BeanDefinition

注册BeanPostProcessor

生成BeanDefinition后,但是里面的属性都是空的。我们知道,在spring中,给Bean属性赋值的过程是在BeanPostProcessor中完成的。

所以,Dubbo生成BeanDefinition之后,还需要一个BeanPostProcessor来为其属性赋值。

调用registerInfrastructureBean方法注册ConfigurationBeanBindingPostProcessor

image.png

下面来看下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的判断逻辑主要为判断BeanDefinitionsource是否为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-providername:dubbo等。

获取BeanBinder对象,调用DataBinder的bind方法进行属性的赋值操作。

赋值操作的简单过程为,以name:dubbo-demo-annotation-provider为例,从configurationProperties对象里获取name值,也就是dubbo-demo-annotation-provider,再从类里面找到name属性,通过反射把dubbo-demo-annotation-provider赋值给ApplicationConfig对象。

后记

关于Dubbo启动解析配置文件,并生成对应的Bean对象到这里接结束了。