Dubbo3.0服务引用原理变化

706 阅读5分钟

路途虽遥远,将来更美好
公众号:九点半的马拉

今年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, truefalse);
        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 方法了。