Dubbo3.0服务引用原理变化

今年Dubbo发布了全新的3.0版本,开始面向云原生,有很多细节与之前较多使用的2.7.x版本有所不同,本文主要介绍Dubbo3.0下的服务引用流程与之前的不同点,一些相同的逻辑就简单介绍下。

何时触发服务引用

Dubbo2.7.x版本触发服务引用的地方有两个,一个是因为 ReferenceBean 实现了 InitializingBean 接口,在 Spring 容器调用 afterPropertiesSet 方法时进行服务引用,使用该方式可通过init属性开启,在最后调用 getObject 方法;一个是对用的服务在其他类中被引用时,直接调用 getObject 方法。Dubbo默认使用后者方式。

Dubbo3.0中进行了一些改动

Dubbo3.0同样继承了 InitializingBean 接口,但是实现逻辑发生了变化,在 afterPropertiesSet 方法中首先通过 BeanFactory 获取 BeanDefinition ,通过 BeanDefinition 获取 interfaceClass 和 interfaceName 等属性值,解析 xml 或者注解形式下的一些属性值。最后获取 ReferenceBeanManager Bean对象,将该 referenceBean 添加到一个 变量名为 referenceConfigMap 的 ConcurrentHashMap 中,并映射 reference key 到 referenceBeanName。

以上是 afterPropertiesSet 方法中的主要逻辑,我们可以发现与2.7.x版本有所不同,3.0版本放弃在该方法中进行服务引用,只提供在 getObject方法中进行懒加载,只创建一个lazy Proxy, 当首次调用代理时才会真正的创建,而

Dubbo2.7.x在 getObject 方法中会直接调用 ReferenceConfig#createProxt 方法,而 Dubbo3.0 会只创建一个lazy Proxy, 并创建ReferenceConfig,在首次调用代理的时候再创建createProxy。

为什么现在只保留懒加载式的方式呢?以下是官方文档给出的解释。

当Spring通过类型搜索Bean时,如果Spring无法确定一个 factory bean 的类型时,它可能会尝试对其进行初始化。而 ReferenceBean 也是一个 FactoryBean。(但是该问题在Dubo中已经通过装饰BeanDefinition解决了)另外,如果一些 ReferenceBeans 依赖很早初始化的bean, 而 dubbo config benas 还没有准备好,如果马上初始化 dubbo reference,可能会出现一些意想不到的问题。所以,在Dubbo3.0中,referencebean 初始化时,只会创建一个 lazy proxy,与dubbo 引用相关的资源不会被初始化。这样就排除了Spring的影响,dubbo配置初始化是可控的了。

Dubbo3.0是如何创建一个 lazy proxy for reference?

答案就在 ReferenceBean#createLazyProxy 方法中。

private void createLazyProxy() {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTargetSource(new DubboReferenceLazyInitTargetSource());
        proxyFactory.addInterface(interfaceClass);
       ....
       ....
        this.lazyProxy = proxyFactory.getProxy(this.beanClassLoader);
 }
private class DubboReferenceLazyInitTargetSource extends AbstractLazyCreationTargetSource {
        @Override
        protected Object createObject() throws Exception {
            return getCallProxy();
        }
        @Override
        public synchronized Class<?> getTargetClass() {
            return getInterfaceClass();
        }
 }

玄机就在 AbstractLazyCreationTargetSource 这个抽象类中,对于里面的 createObject 方法,该抽象类要求子类调用这个方法来返回延迟初始化的对象,第一次调用代理时调用该方法。当您需要将某个依赖项的引用传递给对象但您实际上不希望在首次使用该依赖项之前创建该依赖项时,这很有用。 一个典型的场景是连接到远程资源。这正好解决上述Dubbo3.0中的诉求。

当首次调用代理对象时,最终会调用 getCallProxy 方法。

private Object getCallProxy() throws Exception {
        if (referenceConfig == null) {
            throw new IllegalStateException("ReferenceBean is not ready yet, please make sure to call reference interface method after dubbo is started.");
        }
      
        return referenceConfig.get();
}

这时我们发现用到了 ReferenceConfig 类,该类是服务引用的关键类,那它是在什么时候被创建的呢?

在 ReferenceBean 中,只有一个方法对 ReferenceConfig 进行了赋值。

public void setKeyAndReferenceConfig(String key, ReferenceConfig referenceConfig) {
        this.key = key;
        this.referenceConfig = referenceConfig;
}

继续追踪,在 ReferenceBeanManager#initReferenceBean中调用了该方法,该类是不是很熟悉,就是在上述的 afterPropertiesSet 方法提到了该类,将 ReferenceBean 注册到 ReferenceBeanManager 中。

最终我们发现 DubboConfigBeanInitializer 类 中的 afterPropertiesSet 方法 调用了上述逻辑。

这时我们可能有疑惑,ReferenceBean 和 DubboConfigBeanInitializer 都实现了 InitializingBean 接口,加载的先后顺序不同会不会导致问题的产生。

ReferenceBean#afterPropertiesSet 方法在最后调用 ReferenceBeanManager#addReference 方法,将 ReferenceBean 注册到 一个Map 中,在最后有一个判断,如果 initialized 变量为 true, 则会调用 initReferenceBean 方法, 这个方法在后面会讲到。

referenceBeanMap.put(referenceBeanName, referenceBean);
//save cache, map reference key to referenceBeanName
this.registerReferenceKeyAndBeanName(referenceKey, referenceBeanName);

// if add reference after prepareReferenceBeans(), should init it immediately.
if (initialized) {
   initReferenceBean(referenceBean);
}

DubboConfigBeanInitializer

在 DubboConfigBeanInitializer 中的注释中,提示到 Dubbo Config Bean 必须在注册所有 BeanPostProcessors 之后初始化,也就是在 AbstractApplicationContext#registerBeanPostProcessors 方法之后。

DubboConfigBeanInitializer 实现了 BeanFactoryAware 和 InitializingBean 接口,

其中 BeanFactoryAware 接口提供了一个方法setBeanFactory(BeanFactory beanFactory),通过该方法我们可以知道实现这个接口的bean属于哪个beanFactory。

另一个重要的实现方法是 afterPropertiesSet 方法,里面直接调用init方法。

1)通过 setBeanFactory 方法传入的beanFactory参数变量调用 getBean 方法获取 ReferenceBeanManager 实例,

2)在被@Reference注解的bean加载前确保一些Dubbo的配置Bean被初始化,并添加到 configManager 中,包括ApplicationConfigRegisteryConfigProviderCOnfigConsumerCOnfig类等。初始化的方法很简单,直接调用beanFactory.getBean方法。

private void loadConfigBeansOfType(Class<? extends AbstractConfig> configClass, AbstractConfigManager configManager) {
        String[] beanNames = beanFactory.getBeanNamesForType(configClass, true, false);
        for (String beanName : beanNames) {
            AbstractConfig configBean = beanFactory.getBean(beanName, configClass);
            configManager.addConfig(configBean);
        }
 }
  1. 最后调用 ReferenceBeanManager#prepareReferenceBeans 方法, 在这里调用了上述的 initReferenceBean 方法,创建 ReferenceConfig,将其添加到 referenceConfigMap 中。
 public void prepareReferenceBeans() throws Exception {
       initialized = true;
       for (ReferenceBean referenceBean : getReferences()) {
           initReferenceBean(referenceBean);
 }

在 DubboConfigBeanInitializer 和 ReferenceBean 中 都调用了 initReferenceBean 方法 进行创建 ReferenceConfig, 为什么要在两处呢,在 ReferenceBean 中创建不是更方便呢?

这是因为在 创建 ReferenceConfig 时需要 Dubbo Config Bean 信息,在 initReferenceBean 方法中也注明了该方法应该只在所有dubbo config bean和所有属性解析器加载后调用。

在 DubboConfigBeanInitializer 中会加载这些信息,并将 initialized 变量设置为 true, 所以在 ReferenceBean 中会有个判断,如果 initialized 为 ture, 就可以直接调用 initReferenceBean 方法了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335