Spring Bug深度历险记
前面的一篇文章Spring扩展点(BeanPostProssor)之深度诊断历险记的续集
前情概述
下面我们大概复述一下之前讨论的BeanPostProcessor遇到的问题,具体可以参考前面的一篇文章Spring扩展点(BeanPostProssor)之深度诊断历险记。
前面我们发现自己定义的BeanPostProcessor没有生效,然后各种给Spring趴衣服
后发现原来因为BeanPostProcessor的加载顺序导致的。好了到这,你只需要知道个大概就可以了。下面请系好安全带,我们要发车了。
Spring Async的Bug
看到这个标题是不是虎躯一震,Spring竟然还有Bug?你是不是一定觉得这一定是个标题党。很遗憾,Spring也是人写出来的,它也有Bug。
下面我们我们就来一次Spring Async Bug深度历险:
-
某一天看到opentracing-contrib/java-spring-cloud中的一个issue @Async instrumentation not working when using AsyncConfigurerSupport。这个issue大概是说,当他使用AsyncConfigurerSupport 配置方式来使用Spring Async的时候,链路追踪功能没有生效。然后开源作者和isssu发起者简单聊了一下,但是也没有给出具体的方案。
-
然后我看了下,也没有慢慢深究其原因。后来一想记得spring-cloud-sleuth(全链路追踪Spring-Cloud框架)也支持Spring Async,那这个框架是不是也遇到了同样的问题呢,然后我就很机智地去搜索了下spring-cloud-sleuth项目的issue列表。果然,被我搜索到了一个类似的问题,而且已经被解决了,开心ing。
-
进去看一下AsyncCustomAutoConfiguration does not post process a bean of type AsyncConfigurer,发现确实给出了解决方案,如下
。
意思是说你只需要在你的配置Bean中加上一个注解@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
就搞定了,而且这种解决方案已经被写入到项目文档中了。然后我就屁颠屁颠地去找了下spring-cloud-sleuth的文档spring-cloud-sleuth
然后发现,确实文档中提示你需要加上@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
注解
-
OK,到这,我们是不是可以喝个小酒,唱个小曲了,庆祝一下终于解决了这个问题呢?不,我们是有追求的程序员。这个方案对不对我们可以暂且不用管(实际我自己试了是不管用的),这个方案本真是有不完美的地方的,因为需要提醒用户修改自己的配置类,这对于以零侵入的框架来说不是一个好的解决方案。而且解决方案中的issue中也没有说清楚具体的原因,我们在使用的时候总感觉有点不放心。所以我决定自己去探索一下具体的原因。
5.下面我们来翻一下Spring Async的源代码,大致路径是
@EnableAsync
----->@Import(AsyncConfigurationSelector.class)
----->ProxyAsyncConfiguration
----->AsyncAnnotationBeanPostProcessor
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
bpp.configure(this.executor, this.exceptionHandler);
// ....其他忽略
return bpp;
}
}
我们看到其中有一个非常重要的代码
bpp.configure(this.executor, this.exceptionHandler);
executor和exceptionHandler其实是父类中的类,我们来看一下这两个对象是怎么来的
/**
* Collect any {@link AsyncConfigurer} beans through autowiring.
*/
@Autowired(required = false)
void setConfigurers(Collection<AsyncConfigurer> configurers) {
if (CollectionUtils.isEmpty(configurers)) {
return;
}
if (configurers.size() > 1) {
throw new IllegalStateException("Only one AsyncConfigurer may exist");
}
AsyncConfigurer configurer = configurers.iterator().next();
this.executor = configurer::getAsyncExecutor;
this.exceptionHandler = configurer::getAsyncUncaughtExceptionHandler;
}
OK,简单理一下就是AsyncAnnotationBeanPostProcessor
(implements BeanPostProcessor
)使用@Autowired
注解依赖了AsyncConfigurer
的两个对象Executor
和AsyncUncaughtExceptionHandler
。听到这,你是不是想起点啥呢?如果没有,再说得直白一点:
BeanPostProcessor
依赖了普通Bean。这时候有没有觉得虎躯一震呢?这不就是之前我们前面的博客Spring扩展点(BeanPostProssor)之深度诊断历险记提到的问题吗?
等等,我们之前不是说,这种方式是有问题的吗?难道Spring自己也犯了这个错误吗?
- 带着这个疑问,我又去翻了下Spring-framework的issue列表,还真被我翻到了Doc: AsyncConfigurer causes dependencies to be created early [SPR-16945]
上图中红框中的一句话说出了原因:
AsyncAnnotationBeanPostProcessor
(implements BeanPostProcessor
)依赖了AsyncConfigurer
bean,所以在注册BeanPostProcessor
的时候提前初始化了AsyncConfigurer
,所以导致AsyncConfigurer
没有被其他BeanPostProcessor
处理。
至此,我们已经看到证明了Spring确实在处理这个
AsyncConfigurer
的时候出现了相关的Bug(虽然这个bug正常情况下不会影响到用户的使用)
Spring是如何修复这个问题的
Bug确实有,那Spring是如何修复这个问题的呢?我们翻一下发布记录瞅一下:在4.3.19版本中修复了
那到底是怎么修复的呢,我们去瞅一下代码
貌似跟我的想象有点不太一样,只是加了一个
Supplier
对象来实现对Executor
和AsyncUncaughtExceptionHandler
的懒加载而已,但没有实现对AsyncConfigurer
对象的懒加载。然后我自己试验了一下,我们还是没有能使用BeanPostProcessor
来替换掉AsyncConfigurer
对象。
看到这里是不是一下火气就上来了,这尼玛,绕了半天还是没有解决我们上面issue@Async instrumentation not working when using AsyncConfigurerSupport中提到的问题
但是比较奇怪的是,Spring竟然关掉了这个issue,然后让你以为已经解决了,这个就比较诡异了。反正我到现在都不太明白这个解决方法到底是解决啥问题的。于我们这个问题而言,根本鸟用没有啊。
所以我又去翻了下Spring-framework的issue列表,又发现了一个issueEnableAsync breaks load order of beans [SPR-16919]
提到了这个问题,这一次issue一直没有关闭。
我们怎么解决呢
那我们到底有没有办法解决这个issue @Async instrumentation not working when using AsyncConfigurerSupport呢?
当然我们可以给Spring官方提PR解决这个问题。但是周期长,而且官方对这个问题的态度比较暧昧,不一定接受你的PR(一会是bug一会不是bug)。
那除了这个方法外,还有其他办法吗?
前面的文章Spring扩展点(BeanPostProssor)之深度诊断历险记我们提到了一个非常重要的结论:
被PriorityOrderedBeanPostProcessor所依赖的Bean其初始化时无法享受到PriorityOrdered、Ordered、和nonOrdered的BeanPostProcessor的服务。而被OrderedBeanPostProcessor所依赖的Bean无法享受Ordered、和nonOrdered的BeanPostProcessor的服务。最后被nonOrderedBeanPostProcessor所依赖的Bean无法享受到nonOrderedBeanPostProcessor的服务。
也就是说Bean的生命周期中都要经过BeanPostProcessor的处理,然后是有一定顺序的,主要顺序如下图:
那我们的思路就比较清晰了,我们定义的
BeanPostProcessor
只要在AsyncAnnotationBeanPostProcessor
这个BeanPostProcessor
之前处理Bean就可以了
所以就有了下面的issue对话和PRFix Async instrumentation when using AsyncConfigurerSupport
详细的怎么解决了在PR中都有,这里就不贴出来了,大家有兴趣可以去看一下这个PR
总结
本篇文章有点冗长,但是主要讲了三件事
- Spring Async在对
AsyncConfigurer
对象的处理犯了我们之前文章提到的提前初始化Bean的问题 - 我们看了spring-cloud-sleuth框架也遇到了同样的问题,然后我们发现其实该方案不完美或者根本不生效(我自己试了是根本没用,大家可以自己试一下)
- 然后我们又去看了Spring的问题列表,并且查看了Spring的解决方案,但是最后我们实验依旧不管用,然后我们就自己给出了解决办法。
从这3件事中,我们发现其实往往解决问题的办法很简单也很明确,但是发现问题,并且理清楚内部的原理往往更难而且也更重要