Dubbo无法处理Spring代理对象

背景

在阿里重启开源之前,基于dubbo注解的服务暴露有很多缺陷。公司小伙伴找到我帮他分析一个dubbo服务使用aop不生效的问题,最后分析发现是在使用aop场景下如果接口包含循环依赖,dubbo服务暴露是拿不到代理,所以就导致不生效了。

为了简化背景,业务方同学对外暴露controller接收http请求, 依赖关系:

1
2
3
4
Controller 
-> DeliveryOperateService
-> CancelDistOrderProcessor
-> DeliveryOperateService

这里大家应该发现循环依赖了, DeliveryOperateService是应用了aop拦截,这种场景在开源版本dubbo 2.5.8之前是无法正确处理的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Controller
@RestController
@RequestMapping("/trade-dc/operate")
public class DeliveryOperateController {

@Resource
private DeliveryOperateService deliveryOperateService;
...
}

// DeliveryOperateService
@com.alibaba.dubbo.config.annotation.Service(protocol = {"dubbo"}, registry = {"haunt"})
public class DeliveryOperateServiceImpl implements DeliveryOperateService {

@Resource
private CancelDistOrderProcessor cancelDistOrderProcessor;
...
}

// CancelDistOrderProcessor
@Component
public class CancelDistOrderProcessor implements IComponent {

@Resource
private DeliveryOperateService deliveryOperateService;
...
}

在正常情况下,如果使用aop在dubbo暴露服务时会传递正确的spring动态代理后的对象:

Dubbo服务暴露实际持有的对象是代理后的对象。但是因为循环依赖Dubbo提前拿到DeliveryOperateService非代理的实例。

我们来看下为什么这种场景Dubbo无法处理?

Step1, Spring启动初始化Controller, 对属性进行注入

Step2, Controller触发DeliveryOperateService创建实例

第一次依赖注入就会触发bean的实例化并且保存在exposedObject中,,注意,这里是非代理对象。

Step3, DeliveryOperateService依赖CancelDistOrderProcessor并触发它初始化

这里也没什么特殊的,在populateBean会触发循环依赖DeliveryOperateService加载,这时候earlySingletonExposure值为true, 代表bean提前暴露。

Step4, 在前一步触发,DeliveryOperateService其实会创建动态代理

循环引用会导致提前暴露earlySingletonExposure=true,这个时候加载的是getEarlyBeanReference,在里面创建spring动态代理:

在创建完动态代理后,DeliveryOperateService会加入earlyProxyReferences,后面再获取这个bean就不会再重复创建代理了。

到此,DeliveryOperateService确实会创建,并且会用在CancelDistOrderProcessor对应注入的字段中。

Step6, 触发Dubbo服务暴露的实例不是代理

因为在CancelDistOrderProcessor中已经触发了代理生成,所以第Step1中的实例不会再创建代理了。

在代码555会触发dubbo AnnotationBean进行服务暴露,但是这个不是代理实例了,但是为什么spring还是正确返回代理后的实例呢?

因为循环引用触发earlySingletonExposure=true, 并且在前面已经生成过动态代理了,可以直接在getSingleton拿到动态代理的返回了。

好了,基本原因已经分析的够清楚了,我觉的有2点注意事项:

  • dubbo原来注解实现声明周期没搞清楚
  • spring如果能提前判断循环引用获取exposedObject也没问题
1
2
3
4
5
6
7
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
}

比如把循环引用逻辑提前到populateBean之前判断一下。

为什么在开源dubbo 2.5.8版本之后没有这个问题?

重写后的注解实现,我深入研究过,新版本实现不会提前使用没有代理的bean,关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ServiceAnnotationBeanPostProcessor
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);

if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
registerServiceBeans(resolvedPackagesToScan, registry);
} else {
if (logger.isWarnEnabled()) {
logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
}
}
}

ServiceAnnotationBeanPostProcessor 实现BeanDefinitionRegistryPostProcessor接口,会在所有spring bean真正初始化前完成dubbo服务的注册,整个生命周期中不会触碰到代理前的对象。

总结

我其实平时不太习惯写文章,但是发现分析后的问题记录下来可以让更多同学收益。

感谢您的阅读,本文由 诣极的博客 版权所有。如若转载,请注明出处:诣极的博客(https://zonghaishang.github.io/2018/10/01/Spring杂谈-循环依赖导致Dubbo服务无法被正确代理/
工作经历
理解Java字节码原理