Dubbo是如何解析@Service、@Reference的?


1. Dubbo启动时的架构和流程

启动原理图
在这里插入图片描述
如上图所示,Dubbo在Spring的启动主要分为以下三大步:

  1. 根据配置文件生成对应的XxxConfig对象,例如ApplicationConfig、ProtocolConfig等等
  2. 扫描并处理@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,分别生成protocolP1ConfigprotocolP2Config多个类,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

  1. 扫描@Service标注的类,得到一个BeanDefinition,一个Spring中的Bean
  2. 在扫描完了之后,会针对所得到的每个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来处理的
在这里插入图片描述
其中DubboComponentScanRegistrarregisterBeanDefinitions()中分别处理的@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框架的一点扩展!

        
最后注册ServiceBeanBeanDefination,然后使用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进行扩展修改的,这点不同于BeanFactoryPostProcessorBeanFactoryPostProcessor是修改或增加Bean定义的

        

4.1 寻找注入点

        由于注入的ReferenceAnnotationBeanPostProcessor类是一个Bean的后置处理器,所以进入postProcessPropertyValues方法中查看对@Component标注的类的扩展,首先要寻找注入点:找到@Reference注解标注在哪个位置上!
在这里插入图片描述
寻找注入点会查看属性方法上是否标有@Reference注解!被@Reference注解的属性或方法都是注入点。
在这里插入图片描述

        

4.1 注入点赋值

        针对某个Bean找到所有注入点之后,就会进行注入了,注入就是给属性或给set方法赋值。此时就会调用ReferenceAnnotationBeanPostProcessordoGetInjectedBean()方法来得到一个对象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注解服务引入的过程是这样的:

  1. 得到当前所引入服务对应的ServiceBeanbeanName(源码中叫referencedBeanName
  2. 根据@Reference注解的所有信息+属性接口类型得到一个referenceBeanName
  3. 如果referenceBeanCache没有ReferenceBean对象,则创建一个ReferenceBean,有则获取
  4. 根据referencedBeanName(ServiceBean的beanName)判断Spring容器中是否存在该bean
    ①:如果存在,则给ReferenceBeanref属性(代理对象)取一个别名,别名为referenceBeanName
    ②:如果不存在 ,则将创建出来的ReferenceBean注册到Spring容器中,由于ReferenceBean是一个FactoryBean,后续可以通过getObject()方法获取到ref代理对象
  5. 通过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注入某个服务!

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值