Spring Bug深度历险记

前面的一篇文章Spring扩展点(BeanPostProssor)之深度诊断历险记的续集

前情概述

下面我们大概复述一下之前讨论的BeanPostProcessor遇到的问题,具体可以参考前面的一篇文章Spring扩展点(BeanPostProssor)之深度诊断历险记

前面我们发现自己定义的BeanPostProcessor没有生效,然后各种给Spring趴衣服后发现原来因为BeanPostProcessor的加载顺序导致的。好了到这,你只需要知道个大概就可以了。下面请系好安全带,我们要发车了。

Spring Async的Bug

看到这个标题是不是虎躯一震,Spring竟然还有Bug?你是不是一定觉得这一定是个标题党。很遗憾,Spring也是人写出来的,它也有Bug。

下面我们我们就来一次Spring Async Bug深度历险:

  1. 某一天看到opentracing-contrib/java-spring-cloud中的一个issue @Async instrumentation not working when using AsyncConfigurerSupport。这个issue大概是说,当他使用AsyncConfigurerSupport 配置方式来使用Spring Async的时候,链路追踪功能没有生效。然后开源作者和isssu发起者简单聊了一下,但是也没有给出具体的方案。

  2. 然后我看了下,也没有慢慢深究其原因。后来一想记得spring-cloud-sleuth(全链路追踪Spring-Cloud框架)也支持Spring Async,那这个框架是不是也遇到了同样的问题呢,然后我就很机智地去搜索了下spring-cloud-sleuth项目的issue列表。果然,被我搜索到了一个类似的问题,而且已经被解决了,开心ing。

  3. 进去看一下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)注解

  4. 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的两个对象ExecutorAsyncUncaughtExceptionHandler。听到这,你是不是想起点啥呢?如果没有,再说得直白一点:

BeanPostProcessor依赖了普通Bean。这时候有没有觉得虎躯一震呢?这不就是之前我们前面的博客Spring扩展点(BeanPostProssor)之深度诊断历险记提到的问题吗?

等等,我们之前不是说,这种方式是有问题的吗?难道Spring自己也犯了这个错误吗?

  1. 带着这个疑问,我又去翻了下Spring-framework的issue列表,还真被我翻到了Doc: AsyncConfigurer causes dependencies to be created early [SPR-16945]

上图中红框中的一句话说出了原因:AsyncAnnotationBeanPostProcessor(implements BeanPostProcessor)依赖了AsyncConfigurerbean,所以在注册BeanPostProcessor的时候提前初始化了AsyncConfigurer,所以导致AsyncConfigurer没有被其他BeanPostProcessor处理。

至此,我们已经看到证明了Spring确实在处理这个AsyncConfigurer的时候出现了相关的Bug(虽然这个bug正常情况下不会影响到用户的使用)

Spring是如何修复这个问题的

Bug确实有,那Spring是如何修复这个问题的呢?我们翻一下发布记录瞅一下:在4.3.19版本中修复了


那到底是怎么修复的呢,我们去瞅一下代码

貌似跟我的想象有点不太一样,只是加了一个Supplier对象来实现对ExecutorAsyncUncaughtExceptionHandler的懒加载而已,但没有实现对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件事中,我们发现其实往往解决问题的办法很简单也很明确,但是发现问题,并且理清楚内部的原理往往更难而且也更重要

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349