之前在搭建一个项目的基础框架的时候,有一个同事和我说我们的自定义线程池未生效,默认使用的是Spring默认线程池,于是看了下线程名称确实是用的是Spring默认线程池
首先去查看了下Spring默认线程池的配置类,该类是一个自动配置类,该线程池Bean属于条件装配,在我们未配置线程池Bean类型的时候才会装配该默认线程池,同时该默认线程池Bean名称配置为applicationTaskExecutor还有一个别名叫做TaskExecutor,而这别名恰巧就是我们的自定义线程名
当时去看了下BeanDefinitionMap中两者都是存在的
于是开始排查我们自定义线程池是否完成了装配,通过Debug跟踪SpringBoot启动上下文装配Bean的流程,发现我们自定义线程池确实装配了但是在Spring默认线程池装配之后,这个当时是因为我们自定义线程池也是放在自动装配类中,通过配置Spring.facories完成自动配置
然后又排查了一下依赖该线程池的业务类装配流程,是先在AliasMap中查找Bean的主名称,再通过主名称去BeanDefinitionMap中拿到Bean定义后去创建Bean从而完成依赖注入
也正是由于依赖的自定义线程池是Spring默认线程池的别名才导致我们项目中的自定义线程池虽然完成装配但却没办法通过依赖注入到相应的业务类中去,从而导致无法使用
最后是通过AutoConfigureBefore调整了自定义线程池的装配顺序在那个默认线程池之前
对于上面的问题,我们需要了解的是
问题描述:
默认线程池:Spring Boot提供了默认的线程池配置,当您没有显式配置自己的线程池时,Spring Boot会自动配置一个。这个默认线程池的Bean名称是applicationTaskExecutor,并且它还有一个别名TaskExecutor。
自定义线程池:您的项目中也有自定义的线程池配置,并且这个自定义线程池也是通过自动配置机制(可能是通过spring.factories文件)来装配的。但是,问题在于您的自定义线程池Bean的名称或别名与Spring Boot默认线程池的别名(TaskExecutor)相同或冲突。
依赖注入问题:在Spring容器中,当业务类需要注入线程池时,它首先会在别名映射(AliasMap)中查找对应的Bean名称,然后通过这个名称在Bean定义映射(BeanDefinitionMap)中找到相应的Bean定义来创建实例并注入。由于您的自定义线程池和默认线程池共享了相同的别名(TaskExecutor),所以Spring容器在注入时选择了先定义的Bean,即默认线程池。
解决方案:
您提到通过AutoConfigureBefore注解解决了问题。这个注解用于指定一个自动配置类的加载顺序,确保它先于其他自动配置类加载。在您的情况下,您可能将这个注解添加到了您的自定义线程池自动配置类上,以确保它在Spring Boot的默认线程池自动配置类之前加载。这样,当业务类需要注入线程池时,它首先会找到您的自定义线程池,因为此时默认线程池还没有被定义。
总结:
确保自定义Bean的唯一性:为了避免冲突和混淆,最好确保您自定义的Bean名称和别名不与Spring Boot或其他库提供的默认Bean相同。
理解Spring的自动配置机制:Spring Boot的自动配置机制非常方便,但也需要我们理解它的工作原理和加载顺序,以便在需要时进行调整。
使用AutoConfigureBefore、AutoConfigureAfter或AutoConfigureOrder:这些注解可以帮助我们控制自动配置类的加载顺序,从而解决由于加载顺序导致的配置冲突问题。