(四) dubbo源码解析---- consumer @Reference 注解注入原理

dubbo 版本: 2.6.0
springboot版本: 2.x
spring 版本:5.x

概述
本文主要介绍下dubbo consumer中 @Reference 注解是如何注入到宿主对象的。
在使用@Reference注解过程中,总是会想几个问题:

  1. 被@Reference 注解的 bean,是在什么时机注入的?
  2. 被@Reference 注解的 bean,通常是一个接口,怎么可以被实例化呢?

答案是:

  1. @Reference 的注入时机和 @Autowired 注解是类似的,但不完全一样。负责修饰 bean 属性的 BeanFactoryPostProcessor不同。
  2. @Reference修饰的域是通过动态代理实现的。也就是生成了一个动态的接口实现类。

    接下来我们通过阅读源代码来分析一下这个具体过程。
上一篇通过分析dubbo @Service注解的解析的源代码,了解到被 @Service注解修饰的类都会被封装为 ServiceBean(ServiceConfig的子类),然后通过ServiceConfig发布服务。通过 dubbo 官方架构图, 发现 dubbo consumer是通过ReferenceConfig去与 dubbo provider建立链接,通过查看继承树,发现ReferenceConfig只有一个子类ReferenceBean。因此猜测(与 @Service 注解类似), 通过ReferenceBean来实例化 consumer代理。
    至于consumer代理的实例化时机, 应该改与@Autowired 注解类似,通过 BeanPostProcessor 来处理。

    目前以上还都是猜测,需要通过阅读源代码去验证。我们定义一个 controller,使它有一个 dubbo service 的属性:

@RestController
@RequestMapping(value ="consumer")
public class ConsumerController {

    @Reference
    HelloService helloService;
    
    @GetMapping("sayHello")
    public String sayHello(@RequestParam String word){
        return helloService.sayHello(word);
    }

}

   观察下helloService是什么时候实例化的,再看看具体是负责实例化的类:在AbstractAutowireCapableBeanFactory类中doCreateBean()方法打一个有条件的断点,当 beanName.equals(“consumerController”)时断住,然后看下BeanWrapper中的 helloService实例什么时候不为空。发现是
initializeBean执行完后

Object exposedObject = bean;
		try {
			populateBean(beanName, mbd, instanceWrapper);
			//这个方法执行完后,consumer代理类被注入完成。
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		

跟进代码,发现是applyBeanPostProcessorsBeforeInitialization()执行了consumer代理的实例化,为了弄清楚是哪个BeanPostProcessor具体操作的,我们加个有条件的断点*(beanName.equals(“consumerController”) && ((ConsumerController) current).helloService != null)*:
在这里插入图片描述
发现是 DubboConsumerAutoConfiguration的 postProcessBeforeInitialization()执行的注入。可是打开DubboConsumerAutoConfiguration的继承路径,发现它并不是一个 BeanPostProcessor 的派生类,那他是如何实现 postProcessBeforeInitialization()方法的呢?。
在这里插入图片描述

继续阅读源代码, 发现它注入了一个匿名内部类,如果有人问起 JAVA 如何实现多继承,那可以告诉它匿名内部类是一种实现方式
在这里插入图片描述

    OK, 至此我们已经知道@Reference 注解修饰的属性是什么时候实例化的了,是通过BeanPostFactory 的postProcessBeforeInitialization()方法(DubboConsumerAutoConfiguration)。至于具体怎么做的,我们再看下这个方法的代码吧。

   try {
   		// 获取到 consumerController 的所有实例,我们这个例子只有一个
          for (Field field : objClz.getDeclaredFields()) {
          //判断该字段是否有 Reference 注解
            Reference reference = field.getAnnotation(Reference.class);
            if (reference != null) {
              DubboConsumerAutoConfiguration.this.initIdConfigMap(DubboConsumerAutoConfiguration.this.properties);
              ReferenceBean<?> referenceBean =DubboConsumerAutoConfiguration.this.getConsumerBean(beanName, field, reference);
              Class<?> interfaceClass = referenceBean.getInterfaceClass();
              String group = referenceBean.getGroup();
              String version = referenceBean.getVersion();
              ClassIdBean classIdBean = new ClassIdBean(interfaceClass, group, version);
              Object dubboReference = DubboConsumerAutoConfiguration.DUBBO_REFERENCES_MAP.get(classIdBean);
              if (dubboReference == null) {
                synchronized (this) {
                  // double check
                  dubboReference =
                      DubboConsumerAutoConfiguration.DUBBO_REFERENCES_MAP.get(classIdBean);
                  if (dubboReference == null) {
                    referenceBean.afterPropertiesSet();
					//获取 dubbo reference,也就是consumer 代理类
                    dubboReference = referenceBean.getObject();
                    DubboConsumerAutoConfiguration.DUBBO_REFERENCES_MAP.put(classIdBean,
                        dubboReference);
                  }
                }
              }
              field.setAccessible(true);
              field.set(bean, dubboReference);
            }
          }
        } catch (Exception e) {
          throw new BeanCreationException(beanName, e);
        }
        return bean;
      }

接下来我们去看下ReferenceBean 的getObject()方法

public Object getObject() throws Exception {
        return get();
 }

get()方法是它的父类 ReferenceConfig的。

 public synchronized T get() {
        if (destroyed) {
            throw new IllegalStateException("Already destroyed!");
        }
        if (ref == null) {
            init();
        }
        return ref;
   }

再看 init 方法:

 private void init() {
        //省略大量代码
        ref = createProxy(map);
      //省略代码
  }

createProxy() :

在这里插入图片描述
getProxy(invoker):
在这里插入图片描述

getProxy(invoker, interfaces) :

在这里插入图片描述
不出意外InvokerInvocationHandler是InvocationHandler的派生类

在这里插入图片描述

至此可以看出@Reference修饰的属性是通过动态代理实例化的对象。

需要注意的是:dubbo 的AbstractConfig(ReferenceConfig 的父类) toString()方法反射的调用了所有的方法。
在这里插入图片描述
如果你用 idea 来 debug ReferenceBean 的代码,如果某些位置设置断点会报错(例如 DubboConsumerAutoConfiguration的postProcessBeforeInitialization方法里的一些行),导致应用起不来,而正常启动则没问题。 因为debug模式下的 idea 会默认调用 toString()方法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值