Dubbo源码深度解析(二)

        接着《Dubbo源码深度解析(一)》继续讲,上篇博客主要讲Dubbo提供的三个注解的作用,即:@EnableDubbo、@DubboComponentScan、@EnableDubboConfig。其中后两个注解是在@EnableDubbo上的,因此在启动类上加上@EnableDubbo注解,等同于加上@DubboComponentScan注解和@EnableDubboConfig注解。并且还讲到了Dubbo的包扫描,以及Dubbo整合SpringBoot后,是如何将配置文件中的dubbo.xxx属性绑定到Dubbo的配置类上的。  

        本篇博文将主要讲@DubboService注解的原理以及Dubbo的SPI机制,其实@DubboService注解的原理,在上篇博文中已经讲过了。这里回顾一下:核心是依赖ServiceClassPostProcessor或者ServiceAnnotationBeanPostProcessor,其中,ServiceAnnotationBeanPostProcessor继承自ServiceClassPostProcessor(非抽象类),都是实现了BeanDefinitionRegistryPostProcessor接口。因此会实现BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry()方法,在该方法中,会扫描指定包下所有被@DubboService注解修饰的类,并往Spring容器中注册一个BeanDefinition对象,其中BeanDefinition的beanClass的值为ServiceBean.class,最终Spring实例化的就是beanClass返回的类型,即ServiceBean,上篇博文最后通过AbstractApplicationContext#getBeansOfType(ServiceBean.class)方法,能获取到一个Bean对象,这跟预期是一致的。

        其实在生成ServiceBean的时候,会用到Dubbo的配置类,在子模块service-provider中,我通过DubboConfiguration,定义了四个配置类,分别是:ApplicationConfig、ProtocolConfig、RegistryConfig和ConfigCenterConfig ,代码如下:

package com.szl.config;

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Dubbo配置类,以下几个类实际上都是 AbstractConfig类的子类,在Spring容器初始化这几个Bean的时候,
 * 由于父类中的 AbstractConfig#addIntoConfigManager()方法是被@PostConstruct注解所修饰的,因此,
 *  该方法会调用被自动调用。
 * </p>
 */
@Configuration
public class DubboConfiguration {

    @Bean
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("service-provider");
        return applicationConfig;
    }

    @Bean
    public ProtocolConfig protocolConfig() {
        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setPort(20880);
        protocolConfig.setName("dubbo");
        protocolConfig.setServer("netty4");
        return protocolConfig;
    }

    @Bean
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress("nacos://127.0.0.1:8848");
        return registryConfig;
    }

    @Bean
    public ConfigCenterConfig configCenterConfig() {
        ConfigCenterConfig configCenterConfig = new ConfigCenterConfig();
        configCenterConfig.setPort(8848);
        configCenterConfig.setProtocol("nacos");
        configCenterConfig.setAddress("127.0.0.1");
        // 使用默认值
        // configCenterConfig.setGroup("DEFAULT_GROUP");
        // configCenterConfig.setConfigFile("application-service-provider.yml");
        configCenterConfig.setNamespace("b1cb3cb8-9c6d-4cfc-a391-26aa0119a421");
        return configCenterConfig;
    }
}

        我在注释中写的很详细,这四个配置类都有一个相同的父类,即AbstractConfig,并且在其父类中,有一个方法是被@PostConstruct注解修饰的。最后在初始化这个Bean的时候会,Spring会自动调用被这个注解修饰的方法,看看AbstractConfig#addIntoConfigManager()方法,代码如下:

857bc0212ef344728250193657e641f0.png

        在该方法中,调用了ApplicationModel.getConfigManager()方法,得到ConfigManager对象,即配置管理器,看看是怎么获取的,代码如下:

70ce246bd2294d33a5a2584ffbcbe4f6.png

        又需要看看ApplicationModel的LOADER属性是怎么获取的,其实就是看ExtensionLoader#getExtensionLoader(FrameworkExt.class),代码如下:

d2f0817a735c4ba1ba12f9a807835018.png

        可知,ExtensionLoader有一个EXTENSION_LOADERS,用作缓存,如果通过传入的Class获取不到,则创建一个对象ExtensionLoader,调用其有参构造方法,传入Class,这里的Class就是FrameworkExt.class,并将创建好的ExtensionLoader放入EXTENSION_LOADERS,方便下次直接获取。具体看看ExtensionLoader的有参构造方法,代码如下: b18d97a90e31407d92e9cab44ab9ebb0.png

        因此又会调用到ExtensionLoader#getExtensionLoader()方法,也就是刚刚前面讲的。只不过此时的type属性为ExtensionFactory.class,然后还是先从EXTENSION_LOADERS中获取,假设是第一次,也获取不到,因此会调用ExtensionLoader的有参构造方法。再次回到上图的方法中,但是不同的是,这次由于type是等于ExtensionFactory.class,则此时objectFactory为null,并且还要调用ExtensionFactory#getAdaptiveExtension()方法,看看该方法,代码如下:

a5916d232fcc43efbd5bdca3dec35ace.png

b1484edda3894dd584c2862199f80b9f.png

97a9425a38294363877d2cdd1aaa58a6.png

c605db50c88b49258d4c017e4839f4fe.png

994842aaddac46dea3f3db80d1b77ff2.png

        看看ExtensionFactory,代码如下:

1dace79f000545eca2df299fbdbfd5e2.png

        ExtensionFactory确实被@SPI注解修饰了,只不过value值为"",因此ExtensionLoader#cacheDefaultExtensionName()方法中,最终设置的cachedDefaultName属性为null。回到ExtensionLoader#loadExtensionClasses(),紧接着就是调用ExtensionLoader#loadDirectory()方法,不过在此之前按,需要先看看ExtensionLoader的strategies属性,代码如下:

4983573706954382b8bf7040d672229d.png

8f92208c9477409aabd29518ea3df183.png

        调用ServiceLoader#load()方法,传入LoadingStrategy.class,代码如下:

2716ad08d90046918932a784912fea32.png

41d2825226db4fad9191b72712f46ec5.png

        通过Java的stream流,遍历调用,new LoadingStrategy接口的实现类,得到对象,放入ExtensionLoader的strategies属性中,那LoadingStrategy接口的实现类是如何获取的呢?其实ServiceLoader是Java的 rt.jar下提供的类,属于Java自己实现的SPI机制,它会从classpath目录下的META-INF/services下,找一个名为LoadingStrategy接口全限定名的文件,即org.apache.dubbo.common.extension.LoadingStrategy,刚好找到了,代码如下:

ec014ab9fa3b4dc1944671947927411b.png

        因此最终实例化的就是这三个实现类,断点验证一下:

5eb60f45e36d49a7add3506e6999d06b.png

        因此,我们如果想实现SPI机制,可以借助于Java自带的ServiceLoader实现,如果想实现更复杂的SPI机制,可以借鉴Dubbo,因为讲到这里,还不全是Dubbo的SPI机制。回到ExtensionLoader#loadExtensionClasses()方法,代码如下:

957400923afc45fe92698ad2de727dd4.png

        这里涉及到加载的路径,看看LoadingStrategy接口的三个实现类,代码如下:

be00706224884164bfc4cab3841d5ac0.png

e8156c26cccb4f0e80083f26d08baa54.png

fa9a60c7a48c44e49099502f15d5d7d2.png

        可以知道,扫描的路径分别是:META-INF/dubbo/internal/ 或 META-INF/dubbo/ 或 META-INF/services/。ok,再看看ExtensionLoader#loadDirectory()方法,该方法是关键,代码如下:

98687fe4938c406985339df06ad3a9ca.png

        可以知道,最终是根据前面的几个路径下,并且拼接接口的全限定名,得到文件路径,通过ClassLoader去找文件,得到urls并进行遍历,最终调用到ExtensionLoader#loadResource()方法,进行真正的加载,看看该方法,代码如下:

fd1d9afdf58a47269e162b2bcd9b51a0.png

ff770a58147b40e6b070534951e29da9.png

        当然,现在看的是dubbo-common下的文件,在其他模块下,可能也存在org.apache.dubbo.common.extension.ExtensionFactory,这里就不去找了。再看看ExtensionLoader#loadClass()方法,代码如下:

38e7794ab05f4811b517b0072e458a10.png

        顺便看看ExtensionFactory接口的实现类,如下:

722d82c7594e4a219fff2cad5c79314d.png

95debbb819674a4697e7aa6c42f689de.png

        由此可知,这两个实现类中,只有AdaptiveExtensionFactory是被@Adaptive注解修饰的。再回到ExtensionLoader#getAdaptiveExtensionClass()方法中,代码如下:

ebe0be33eb95485cb630545e80358786.png

        因此会返回AdaptiveExtensionFactory.class,再回到ExtensionLoader#createAdaptiveExtension()方法中,代码如下:

c586899fa0484a1caf1a86d1f0a0ce5e.png

        调用ExtensionLoader#injectExtension()方法,进行Dubbo的依赖注入,代码如下:
d7081a6eb2604d58b667e138bab34735.png

fd5e65b2e6b845668bacffed416a100d.png

        由于AdaptiveExtensionFactory中没有Setter方法,因此无需进行依赖注入,但是它的无参构造中,做了一些初始化处理,无参构造为:

873df9b2ce104ce299c58c77c18102c8.png

        需要看看ExtensionLoader#getSupportedExtensions()方法,代码如下:

951a665034ac43cdb7e2934065b2d58f.png

dfbac50821fe4cd5a03d6f2053971ed5.png

        这也是刚刚讲过的方法,最终只需要看ExtensionLoader#loadClass()方法,代码如下:

13fa0acbeaa743f58b883bc26d3d75dd.png

        因此AdaptiveExtensionFactory不会被放入AdaptiveExtensionFactory的factories属性中,而是ExtensionFactory接口的其他实现,分别在两个文件中,如下:

747fcc06d8f746789d44dca461d5dc98.png

4b9a73f5a50349fd8bfdc219a28741cf.png

        打断点验证一下,结果如下:

c7fa5b3c10f445e6bd1577907f3828eb.png

        这两个对象,即:SpringExtensionFactory和SpiExtensionFactory,将会是实现Dubbo的依赖注入的关键,看名字也能知道:前一个是通过Spring容器寻找依赖的对象;而后一个则是通过Dubbo的SPI机制寻找依赖的对象。当然,寻找依赖并不是直接通过这两个类来寻找的,而是通过AdaptiveExtensionFactory#getExtension()方法来寻找的,代码如下:

b5a1c3d57d8549e7b704d9a52d7c4b7c.png

f29424aeb9a6457dadb67c7032356183.png

        而SpringExtensionFactory的CONTEXTS属性,则是在ReferenceBean中或者ServiceBean总进行赋值的:

2224f12c13c144b4999bdd97d6071fa5.png

e651f132917242ea8d2f00df4b866ec8.png

     到这里为止,Dubbo的SPI机制大概讲清楚了,Dubbo的SPI剩余的内容,后面碰到再补充。想彻底搞懂这块,建议自己跟着源码,再配合博客一起看。OK,回到ApplicationModel#getConfigManager()方法传入的是 name,代码如下:

70b38bc08f9b41d698eb73d874de38dd.png

d0123574153841cdb853322fdc363d9c.png

2536f97635854fbd9091e5c808040837.png

        加载FrameworkExt接口实现类的逻辑跟ExtensionFactory类似,也是在classpath下,找文教名叫org.apache.dubbo.common.context.FrameworkExt的文件,如下:

c3a207ef253e457eb3450d46ecb5c8dd.png

        最终得到这三个实现类的Class的集合,打断点结果如下:

faff763e654d43018382f321b330c355.png

        回到ExtensionLoader#createExtension()方法中,代码如下:

abc0328e72e04d11880e430014cd039d.png
        通过name,即"config"可以获取到Class,即ConfigManager.class,然后通过EXTENSION_INSTANCES获取,由于是第一次,当然获取为空,因此进入if代码块,通过反射,实例化ConfigManager,并放入EXTENSION_INSTANCES中,其中key为"config",然后调用ExtensionLoader#injectExtension()方法,实际上就是调用AdaptiveExtensionFactory#getExtension()方法,寻找依赖,这块上文讲的很清楚了,这里不过多赘述。而ConfigManager有如下的Setter方法,代码如下:

77f19d7a75ef48bd8fb17e383489c85a.png

        这些Setter方法也不一定都会被调用,只有获取到了相应的对象,才会通过反射,进行赋值。需要说的是,给Setter方法赋值是不是一定通过Dubbo提供的依赖注入进行的赋值?这也不一定,可能在其他地方进行赋值,只不过Dubbo提供了这种方式。比如我打断点的时候,发现就不是通过Dubbo的方式进行的依赖注入,断点如下:

b79fc24f55c948ae99715b6a99f4fc5b.png

        OK,至于是哪种方式调用Setter方法进行赋值的,这里不过多纠结,这并非核心流程。回到最开始的地方,即AbstractConfig#addIntoConfigManager()方法,由于该方法被@PostConstruct注解修饰,因此会自动被调用,断点如下:

288cb1491b064e12ace13778887f8d0f.png

bb77f11bb67349249d8a5bffa7b20738.png

        在上篇博客中,讲到了往Spring容器中,添加了两个监听器,分别是:DubboBootstrapApplicationListener、DubboLifecycleComponentApplicationListener,这两个监听器会监听两种事件,分别是ContextRefreshedEvent、ContextClosedEvent。先看看DubboBootstrapApplicationListener,代码如下:

1ef79ab93c4a4d90bc886dda601cc019.png

2221e074073a41b5be4a958a09e2c1fb.png

        先看看DubboBootstrap#initialize()方法,代码如下:

f63335f6cfc943df8ba8fab43a929713.png

        在该方法中,又调用了七个方法,分别看看,代码如下:

① DubboBootstrap#initFrameworkExts()方法:

a93c371872c048deab0f70b9203b7bbb.png

f80d1d93225545e9b430f880d8328dd1.png

        处理默认的配置中心,这里没有配置,因此均为空:

72c1c6dd41544939809dde13d5a99ad5.png

② DubboBootstrap#startConfigCenter()方法:

9e0800bc6275476d8e9671ab20cceab7.png

        先看看我在DubboConfiguration配置的配置中心配置类ConfigCenterConfig,代码如下:

73997557584b404489b69bad107ffa43.png

        其中,group、configFile、namespace我都没有指定,将使用默认值,当然也可以种指定,默认值分别为:

4fa577865c664ab39482cf242626fc84.png

2f89230f846e4fed9daebf7b3b627001.png

080c2546478449f186817c9d3287e7e4.png

        然后我在Nacos上的配置为如下:

de1bfda192554f13b995f107f666667a.png

       dubbo.properties 内容为:

e94ac5ac9e874a9fa09093f3e86f5821.png

        目前我看的Dubbo版本应该是不支持yml/yaml的,继续往下看,重点看看DubboBootstrap#prepareEnvironment()方法,代码如下:

d3f13a94c879411c98301cacd62c2ff7.png

ff8c29114a054003bf9d69e2d806cdbb.png

3aaae5287acb46d286f7335fd6e0a9dd.png

        顺便提了下,为了实现配置中心的功能,我在pom文件中加了一个依赖:

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-configcenter-nacos</artifactId>
            <version>2.7.10</version>
        </dependency>

        因此,我应该在该模块中找一个文件名为org.apache.dubbo.configcenter.support.nacos.NacosDynamicConfigurationFactory,也确实让我找到了,如下:

58dd943252564f73917954c71c8029a3.png

        而它的名字也确实是nacos,符合预期,OK,回到代码继续往下看:

f8624ad168604e79b9a232560a5f8699.png

cc72391f738f439689df773fa41a9781.png

        地址是怎么拼接的,也简单,就不带大家看了,感兴趣的自己看看。再回到DubboBootstrap#prepareEnvironment()方法,看看DynamicConfiguration#getProperties(),代码如下:

e7a9f59472ab4564b3d14dc77d96f079.png

1fba77e8cef34aa9a3944e223d9719e7.png

0b231416f72d42b1adf2ce210c36848d.png

9d81184ad79a44d289718c3ab3ca0cc2.png

c14741acba204b82a1748387d9a634d6.png

2ce59ca96c144b959f914ebf9ad29dcb.png

b289f90e650d44b2a7c804e29721cada.png

        讲到这里就很清晰了,不必深究,因为后面我会专门写两篇博客,讲Nacos的注册中心源码和配置中心源码,敬请期待。再回到DubboBootstrap#prepareEnvironment()方法,打断点看看是否获取到了配置,如下:

791b1a3f9d814b5e8766e5fff4dbe005.png

        结果符合预期,剩下的就是解析获取到的内容,也就是调用ConfigurationUtils#parseProperties()方法,代码如下:

6a07433142be46478da53d7e6f519a37.png
        最后调用Environment#updateExternalConfigurationMap()方法,将解析后的配置,即Map,设置到Environment的externalConfigurationMap属性中。191ac4f3432e41f29f669c4dd07c1d02.png

并且我没有看到把这些属性设置到Spring的环境变量对象中去的代码,因此,很可能这些配置只能从Dubbo的环境变量对象获取到,但是无法从Spring的环境变量对象获取到对应的属性值,这个也可以打断点验证一下:

271510ad14704bc98cdf15d20e92f9f3.png

096cace4bbe7447d959c7c11d799bb9a.png

        确实如此,这是一个问题,还有一个问题是,如果我修改了dubbo.properties的内容,服务感知不到,需要自己实现(下文我做个示例,如何实现),这个就有点坑爹。当然我看的这个版本确实有些地方还不完善,后续版本肯定会做处理的,后面有空我再研究下Dubbo 3.X的版本,如果支持了,我在本篇博客中补充一下。至于感知Nacos配置中心是如何做到了,我也提一嘴,代码如下:

488fc739855e409db9291dc8a3f8690c.png

73db36b9f4dd4a9aafbf85c5f7ff305a.png

0f2a2b6889bc4e4ea96e83281e9bd21b.png

8035ac87e0c64ffdaa37be09291b16ab.png

        我在这里处理做个示例,代码如下:

8889c98c0583444a90c55712ac7baa92.png

        修改Nacos配置,看看结果:

9a5c41b69b6249d5840facec2061734e.png

2ee1a7578ab5491cb6e8c1cfc8080ac7.png

        从修改到感知需要花4~5s的样子,反正获取的是全量的配置。然后可以直接调用之前讲的ConfigurationUtils#parseProperties()方法解析配置,再调用Environment#updateExternalConfigurationMap()方法,更新配置即可。

        限于篇幅,剩下的内容将在下一篇博客中继续讲,敬请期待,如有错误,还望在留言中指正,感谢!下一篇博客地址为:《Dubbo源码深度解析(三)

  • 21
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值