Spring Bug深度历险记

2019-10-15  本文已影响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

总结

本篇文章有点冗长,但是主要讲了三件事

从这3件事中,我们发现其实往往解决问题的办法很简单也很明确,但是发现问题,并且理清楚内部的原理往往更难而且也更重要

上一篇下一篇

猜你喜欢

热点阅读