dubbo 版本: 2.6.0
springboot版本: 2.x
spring 版本:5.x
概述
本文主要介绍下dubbo consumer中 @Reference 注解是如何注入到宿主对象的。
在使用@Reference注解过程中,总是会想几个问题:
- 被@Reference 注解的 bean,是在什么时机注入的?
- 被@Reference 注解的 bean,通常是一个接口,怎么可以被实例化呢?
答案是:
- @Reference 的注入时机和 @Autowired 注解是类似的,但不完全一样。负责修饰 bean 属性的 BeanFactoryPostProcessor不同。
- @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()方法。