Dubbo 3.0 : Dubbo Reference 的扫描

2,548 阅读3分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…

一 . 前言

前一篇看了 Server 段 DubboService 的扫描逻辑 , 这一篇看一下 Dubbo Reference 的扫描逻辑 , 其实是类似的 ,所以文章会偏精简

二 . 扫描的过程

2.1 应用案例

public class DemoServiceComponent implements DemoService {

    @DubboReference
    private DemoService demoService;

    @Override
    public String sayHello(String name) {
        return demoService.sayHello(name);
    }
}

2.2 DubboReference 的扫描

此环节来看一下 , Client 端如何创建一个代理类来代理对应的 @DubboReference 接口的

2.2.1 Reference 扫描的起点

// Reference 扫描的起点为 DubboBootstrap , 其中有这样一段代码
public DubboBootstrap start() {

    //.........
    
    // 发起 Refer Services 处理
    referServices();
    
    //.........
     
}


private void referServices() {
    if (cache == null) {
        cache = ReferenceConfigCache.getCache();
    }

    // 2.2.2 : configManager 中 reference 是之前扫描出来
    configManager.getReferences().forEach(rc -> {
        
        // 为其设置容器 , 并且刷新
        ReferenceConfig<?> referenceConfig = (ReferenceConfig<?>) rc;
        referenceConfig.setBootstrap(this);
        if (!referenceConfig.isRefreshed()) {
            referenceConfig.refresh();
        }

         //............... 后面的逻辑先不关注
    });
}

CacheManager 参数详情

Dubbo-cacheManger.png

2.2.2 References 的扫描过程

下面来看一下 References 是如何扫描出来的

Step 1 : Spring 管理时的创建

在这个环节中 ,对应的类进行 Bean 的初始化 , 而 Reference 起点就是 ReferenceAnnotationBeanPostProcessor 中对 postProcessPropertyValues 进行处理

@Override
public PropertyValues postProcessPropertyValues(
        PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {

   
    AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
    prepareInjection(metadata);
    // bean : DemoServiceComponent 也就是前面的 Bean 实体类
    // beanName : demoServiceComponent
    metadata.inject(bean, beanName, pvs);
  
    return pvs;
}

来看一下 metadata 的结构 : (此处开始要对成员进行处理了)

image.png

Step 2 : 成员变量的反射

protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
    
    // 获取其中的成员变量 , 即为 @DubboReference 对象
    Object injectedObject = getInjectedObject(attributes, bean, beanName, getInjectedType(), this);

    // TODO : 此处后续再看
    if (member instanceof Field) {
        Field field = (Field) member;
        ReflectionUtils.makeAccessible(field);
        field.set(bean, injectedObject);
    } else if (member instanceof Method) {
        Method method = (Method) member;
        ReflectionUtils.makeAccessible(method);
        method.invoke(bean, injectedObject);
    }
}

此处获取注入的 Bean

// 
protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
                                   AnnotatedInjectElement injectedElement) throws Exception {
                                   
    // injectedElement.injectedObject -> demoService
    return getBeanFactory().getBean((String) injectedElement.injectedObject);
}

Step 3 : reference 的处理主流程

首先得说一下 getBean 的过程 , 其 createBean 时获取的 mbd 为 ReferenceBean

C- ReferenceBean
public void afterPropertiesSet() throws Exception {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();

        // INTERFACE_CLASS -> interfaceClass -> 
        // INTERFACE_NAME -> interfaceName
        
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition(getId());
        this.interfaceClass = (Class<?>) beanDefinition.getAttribute(ReferenceAttributes.INTERFACE_CLASS);
        this.interfaceName = (String) beanDefinition.getAttribute(ReferenceAttributes.INTERFACE_NAME);
        
        // Constants.REFERENCE_PROPS -> referenceProps
        if (beanDefinition.hasAttribute(Constants.REFERENCE_PROPS)) {
            // @DubboReference annotation at java-config class @Bean method
            // @DubboReference annotation at reference field or setter method
            // 可以看到 , 支持 JavaConfig 类和 属性以及 Setter 注入
            referenceProps = (Map<String, Object>) beanDefinition.getAttribute(Constants.REFERENCE_PROPS);
        } else {
            // 此处是不同注册类型的处理方式
            if (beanDefinition instanceof AnnotatedBeanDefinition) {
                // Return ReferenceBean in java-config class @Bean method
                ReferenceBeanSupport.convertReferenceProps(referenceProps, interfaceClass);
                if (this.interfaceName == null) {
                    this.interfaceName = (String) referenceProps.get(ReferenceAttributes.INTERFACE);
                }
            } else {
                // xml reference bean
                propertyValues = beanDefinition.getPropertyValues();
            }
        }
        
        // -> BEAN_NAME = "dubboReferenceBeanManager";
        ReferenceBeanManager referenceBeanManager = beanFactory.getBean(ReferenceBeanManager.BEAN_NAME, ReferenceBeanManager.class);
        referenceBeanManager.addReference(this);
    }

Step 4 : reference 的管理流程

// C- ReferenceBeanManager
public void addReference(ReferenceBean referenceBean) throws Exception {
    String referenceBeanName = referenceBean.getId();

    PropertyResolver propertyResolver = applicationContext.getEnvironment();

    String referenceKey = ReferenceBeanSupport.generateReferenceKey(referenceBean, propertyResolver);
    
    ReferenceBean oldReferenceBean = referenceIdMap.get(referenceBeanName);
    // 这里有个细节点 ,通过 != 判断 ,  但是!= 比较的是地址引用 ,而不是像 equal 比较的对象
    // 这里我理解就是对象地址不同 , 意味着创建了重复的 Bean  , 也就是说不能创建同样的实例
    if (oldReferenceBean != null) {
        if (referenceBean != oldReferenceBean) {
            String oldReferenceKey = ReferenceBeanSupport.generateReferenceKey(oldReferenceBean, propertyResolver);
            throw new IllegalStateException("Found duplicated ReferenceBean with id: " + referenceBeanName +
                    ", old: " + oldReferenceKey + ", new: " + referenceKey);
        }
        return;
    }
    
    // private Map<String, ReferenceBean> referenceIdMap = new ConcurrentHashMap<>();
    // 这是一个通过 id/name 指向 ReferenceBean 的对象
    referenceIdMap.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);
    }
}

有了 ReferenceBean , 后续要创建 ReferenceConfig 对象了 , 该对象后续会成为 invoke 的基础配置对象

private synchronized void  initReferenceBean(ReferenceBean referenceBean) throws Exception {

    if (referenceBean.getReferenceConfig() != null) {
        return;
    }

    // reference key -> ReferenceBean:org.apache.dubbo.demo.DemoService()
    String referenceKey = ReferenceBeanSupport.generateReferenceKey(referenceBean, applicationContext.getEnvironment());

    ReferenceConfig referenceConfig = referenceConfigMap.get(referenceKey);
    if (referenceConfig == null) {
        // 创建 实际 ReferenceConfig 对象 , 通过 ReferenceCreator 进行创建
        // 这里获取到了注解中的属性
        Map<String, Object> referenceAttributes = ReferenceBeanSupport.getReferenceAttributes(referenceBean);
        referenceConfig = ReferenceCreator.create(referenceAttributes, applicationContext)
                .defaultInterfaceClass(referenceBean.getObjectType())
                .build();

        // 如果不是生成的名称,请设置id
        if (referenceBean.getId() != null && !referenceBean.getId().contains("#")) {
            referenceConfig.setId(referenceBean.getId());
        }

        // cache referenceConfig
        //  private Map<String, ReferenceConfig> referenceConfigMap = new ConcurrentHashMap<>();
        referenceConfigMap.put(referenceKey, referenceConfig);

        // register ReferenceConfig -> Step 5
        DubboBootstrap.getInstance().reference(referenceConfig);
    }

    // associate referenceConfig to referenceBean
    referenceBean.setKeyAndReferenceConfig(referenceKey, referenceConfig);
}

注释很完善 , 整个流程也就很清晰了 , 下面来看一下往Cache 缓存的逻辑

Step 5 : Config Manager 管理


public DubboBootstrap reference(ReferenceConfig<?> referenceConfig) {
    // 设置容器
    referenceConfig.setBootstrap(this);
    // 添加当前Config -> <dubbo:reference id="demoService" />
    // 这里很有意思 , 使用的还是 xml 格式 ,我猜测应该是和老版本保存统一 , 会不会是利于升级 ?
    configManager.addReference(referenceConfig);
    return this;
}

// 调用下方
public void addReference(ReferenceConfigBase<?> referenceConfig) 
protected <T extends AbstractConfig> T addConfig(AbstractConfig config, boolean unique) {
   
   // 省略判空逻辑
    return (T) write(() -> {
        Map<String, AbstractConfig> configsMap = configsCache.computeIfAbsent(getTagName(config.getClass()), type -> newMap());
        return addIfAbsent(config, configsMap, unique);
    });
}

Step 6 : 使用

这里就可以看到 , 最后已经放在了 Cache 中 , 也就是最开始获取的地方 , 这又是怎么处理出来的呢 ?

三 . 细节补充

3.1 ReferenceBean 的创建

这里getBean 的时候 ,反射出来的是 ReferenceBean , 这其实是 Spring IOC 的相关逻辑

TODO : 还有点多 ,开个单章吧 , 看一下 Dubbo 如何把 Spring 用活的!!!

总结

下一篇来看看一个 Reference 对象是怎么转变为 Invoke 对象的