美文网首页
Spring Bug深度历险记

Spring Bug深度历险记

作者: 一帅 | 来源:发表于2019-10-15 15:56 被阅读0次

    前面的一篇文章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件事中,我们发现其实往往解决问题的办法很简单也很明确,但是发现问题,并且理清楚内部的原理往往更难而且也更重要

    相关文章

      网友评论

          本文标题:Spring Bug深度历险记

          本文链接:https://www.haomeiwen.com/subject/erirmctx.html