【dubbo学习系列】dubbo消费端的代理生成详解(@DubboReference和@Reference)

spring中dubbo实现RPC

在使用dubbo的过程中,我们的用法是在Service中引入 远程接口,然后标记上注解写入名称与版本号等一系列配置信息,然后在需要进行RPC通信的地方调用接口方法,然后就能获取到信息了。
那么按照这种思路,就应该有下列几个步骤需要完成

  1. 发现使用dubbo注解的成员与方法并托管于spring容器
  2. 在对象实例中找到有指定注解的成员变量或方法
  3. 通过动态代理的方式,编织通信代码到访问逻辑中

当然,上述是主要脉络,细分的话还有 序列化问题,地址的负载均衡问题,访问策略问题等等,本文主要讲述的就是上面的主要脉络。

这里主要是对@Reference和@DubboReference不同点做分析

更好的表述其实应该是新老版本的区别,这里@Reference就代指于旧版本2.7.7以下,@DubboReference就代指新版本2.7.7以上。后文也以@Reference和@DubboReference区分新旧

如何加载@Reference和@DubboReference注解

注意,@Reference已被废弃,2.7.7后使用@DubboReference

在springboot中我们会在启动类上添加 @EnableDubbo这个注解,而这个注解中有两个功能性注解 @EnableDubboConfig
@DubboComponentScan
前者负责dubbo配置的初始化,后者负责扫描dubbo配置中指定的包范围,也就是DubboComponentScanRegistrar.class类的职责

@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
}
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
}

DubboComponentScanRegistrar

在源码中,作者标注出了这个类需要关注的几个关键类和注解

/**
 * Dubbo {@link DubboComponentScan} Bean Registrar
 *
 * @see Service  注解,老版本dubbo标注的服务方Service注解
 * @see DubboComponentScan  注解,用于指定dubbo的管理范围,也就是扫描范围
 * @see ImportBeanDefinitionRegistrar  自身实现的接口,在spring的bean定义注册时期进行处理逻辑
 * @see ServiceAnnotationPostProcessor 对扫描路径的处理
 * @see ReferenceAnnotationBeanPostProcessor 处理dubbo注解,并生成代理类,【主要关注点】
 * @since 2.5.7
 */
 public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
 }

主要关注registerBeanDefinitions方法

    @Override
   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

       // initialize dubbo beans
       //初始化信息
       DubboSpringInitializer.initialize(registry);
       //获取需要扫描的包路径,这个方法主要关注点就是,包路径的优先级关系与默认值问题
       Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
       //为后续扫描上面获取的包路径做准备
       registerServiceAnnotationPostProcessor(packagesToScan, registry);
   }

先来看看第二步包路径优先级问题 ,这个问题很简单

  • 先看@DubboComponentScan
  • 在看@EnableDubbo
  • 最后如果都没有,则选用启动类的包路径

所以一般我们不设置包路径就是因为默认会扫描 启动类的包路径,也就会查询我们项目下面所有的类

   private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
       // get from @DubboComponentScan
       Set<String> packagesToScan = getPackagesToScan0(metadata, DubboComponentScan.class, "basePackages", "basePackageClasses");

       // get from @EnableDubbo, compatible with spring 3.x
       if (packagesToScan.isEmpty()) {
           packagesToScan = getPackagesToScan0(metadata, EnableDubbo.class, "scanBasePackages", "scanBasePackageClasses");
       }

       if (packagesToScan.isEmpty()) {
           return Collections.singleton(ClassUtils.getPackageName(metadata.getClassName()));
       }
       return packagesToScan;
   }

第三步,就是创建ServiceAnnotationPostProcessor类的bean定义,然后将其注册到spring中

    private void registerServiceAnnotationPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) {

        //针对ServiceAnnotationPostProcessor类,创建bean定义
        BeanDefinitionBuilder builder = rootBeanDefinition(ServiceAnnotationPostProcessor.class);
        //添加构造参数为包路径
        builder.addConstructorArgValue(packagesToScan);
        builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);
    }

包路径的扫描,过滤等处理就交由ServiceAnnotationPostProcessor来向下进行,后续就不在展开讲解该类了,有兴趣的可以自行查看。

概括

DubboComponentScanRegistrar类主要目的就是,将托管范围内的类,并入spring中。

发现@Refrence和@DubboReference注解,并编织RPC通信逻辑

上述流程完成了第一步,将dubbo的相关注解纳入spring的管理,下一步就是借助spring来发现相关注解并进行一些逻辑的编织生成代理对象。

ReferenceAnnotationBeanPostProcessor(核心)

该类是重点类,是dubbo逻辑编织处理类,这里要说明下,@Reference和@DubboReference的处理是有很大区别的(新老版本),背后的思想方式很有借鉴性。

/**
* 该类继承AbstractAnnotationBeanPostProcessor,核心逻辑分为两步
* 第一,发现被dubbo注解标识的成员变量,方法,或者类
* 第二,生成动态代理类,注入成员变量或方法,或者生成类(Bean)放入spring容器
**/
public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBeanPostProcessor{
}
@Reference

这里 @Reference 发现主要借助于,postProcessMergedBeanDefinition方法在Bean创建之前合并定义的时候进行判断该类中是否有指定注解,如果有则持有其反射对象,待后面通过 postProcessPropertyValues 统一处理 (这种方式,在spring中与使用@Value注解流程一致)。

下面是第一步 postProcessMergedBeanDefinition的处理流程:

    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
        if (beanType != null) {
            if (isReferenceBean(beanDefinition)) {
            ...
            } else if (isAnnotatedReferenceBean(beanDefinition)) {
            ...
            } else {
                //上面几个判断主要是对于@DubboReference的处理
                //第一步,找到当前类中使用@Reference的成员变量和方法
                AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
                metadata.checkConfigMembers(beanDefinition);
                try {
                    //第二步,放入容器中
                    prepareInjection(metadata);
                } catch (Exception e) {
                    throw new IllegalStateException("Prepare dubbo reference injection element failed", e);
                }
            }
        }
    }

而容器就是该类上面的两个成员变量

	//管理成员变量
    private final ConcurrentMap<InjectionMetadata.InjectedElement, String> injectedFieldReferenceBeanCache =
            new ConcurrentHashMap<>(CACHE_SIZE);
	//管理方法
    private final ConcurrentMap<InjectionMetadata.InjectedElement, String> injectedMethodReferenceBeanCache =
            new ConcurrentHashMap<>(CACHE_SIZE);

这里有个注意点,对于静态成员变量dubbo是不会进行处理的

    private List<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> findFieldAnnotationMetadata(final Class<?> beanClass) {
		...
		 if (Modifier.isStatic(field.getModifiers())) {
			 if (logger.isWarnEnabled()) {
				logger.warn("@" + annotationType.getName() + " is not supported on static fields: " + field);
			}
		 	return;
		 }
		...
    }

如果需要设置为静态成员变量,只能通过访问方法,然后在其中设置,注意:这种使用方式,将会使你RPC通信内部的信息共享,导致不可预见的问题,请避免使用。

到这里,需要进行反射持有的对象已经放入容器中了,下一步就行进行代理生成并注入 。

下面是 postProcessPropertyValues的处理流程 , 也是两步
第一步,从容器中获取反射对象,如果没有,则重复发现步骤
第二步,就是进行动态代理,并注入

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

        try {
            //第一步,从容器中获取反射对象,如果没有,则重复发现步骤
            AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
            prepareInjection(metadata);
            //第二步进行注入
            metadata.inject(bean, beanName, pvs);
        } catch (BeansException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Injection of @" + getAnnotationType().getSimpleName()
                    + " dependencies is failed", ex);
        }
        return pvs;
    }

而第二步方法主要是调用实现父类的doGetInjectedBean方法,注意:下面是旧版本中的代码

    @Override
    protected Object doGetInjectedBean(Reference reference, Object bean, String beanName, Class<?> injectedType,
                                       InjectionMetadata.InjectedElement injectedElement) throws Exception {

        String referencedBeanName = buildReferencedBeanName(reference, injectedType);
		//生成ReferenceBean,这个bean是一个FactoryBean,这里并没有将其托管与Spring容器
        ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referencedBeanName, reference, injectedType, getClassLoader());

        cacheInjectedReferenceBean(referenceBean, injectedElement);
		//生成代理类
        Object proxy = buildProxy(referencedBeanName, referenceBean, injectedType);
		//返回该对象
        return proxy;
    }

到此,注解对象就注入完毕了。

@DubboReference

@DubboReference 的发现则需要借助于spring容器,通过手动创建ReferenceBean托管于Spring容器,后续通过spring的 @Autowired 注解获取实例 详细请看@DubboReference的注释

在DubboReference中呢,也兼容了老版本的注解 ,doGetInjectedBean 变更为了直接从BeanFactory中获取

    @Override
    protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
                                       AnnotatedInjectElement injectedElement) throws Exception {

        if (injectedElement.injectedObject == null) {
            throw new IllegalStateException("The AnnotatedInjectElement of @DubboReference should be inited before injection");
        }

        return getBeanFactory().getBean((String) injectedElement.injectedObject);
    }

在老注解@Reference不变的情况下,只需要自己的Configuration类中注入对应的ReferenceBean就OK了。

@Reference和@DubboReference的区别

这里其实已经可以看出明显思路的变化了,@Reference设计上是游离在Spring容器之外的,直接通过反射的方式进行增强,不依托于spring的容器,而 @DubboReference 则通过将FactoryBean融入到Spring环境中,真正的像使用本地方法一样,进行远程访问。

@DubboReference的使用方式

参考@DubboReference注解上面的注释就好了。

* Step 1: Register ReferenceBean in Java-config class:
 * <pre class="code">
 * @Configuration
 * public class ReferenceConfiguration {
 *     @Bean
 *     @DubboReference(group = "demo")
 *     public ReferenceBean<HelloService> helloService() {
 *         return new ReferenceBean();
 *     }
 *
 *     @Bean
 *     @DubboReference(group = "demo", interfaceClass = HelloService.class)
 *     public ReferenceBean<GenericService> genericHelloService() {
 *         return new ReferenceBean();
 *     }
 * }
 * </pre>
 *
 * Step 2: Inject ReferenceBean by @Autowired
 * <pre class="code">
 * public class FooController {
 *     @Autowired
 *     private HelloService helloService;
 *
 *     @Autowired
 *     private GenericService genericHelloService;
 * }
 * </pre>

总结

依托于Spring容器的好处在于职责的划分更加清晰,dubbo本身只负责于rpc的通信,而bean的管理交还给spring,而且使ReferenceAnnotationBeanPostProcessor类更加轻,dubbo的关注点更加集中。

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

唐芬奇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值