通过这篇文章,你可以学会:
SpringBoot组件扫描和自动配置的全流程 SpringBoot组件扫描的路径顺序是如何确定的 条件注解在上述流程中是怎么生效的 条件注解在使用时有什么坑
使用的spring-boot-starter-parent版本为:2.4.4。展示的代码做了简化,隐藏了业务相关信息,但不影响理解。
问题背景
最近在工作中遇到了一个问题,在SpringBoot项目启动的时候出现了报错,具体信息如下:
markdown复制代码***************************
APPLICATION FAILED TO START
***************************
Description:
The bean 'initJackson', defined in class path resource [com/peng/project/JacksonConfig.class], could not be registered. A bean with that name has already been defined in class path resource [com/peng/project/thirdparty/JacksonConfig.class] and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
很明显,有两个被 @Configuration 注解标记的 JacksonConfig,它们都有一个同名的 initJackson 方法,所以在启动的时候报错了。奇怪的是,其中一个initJackson方法是用了 @ConditionalOnMissingBean 注解标记的,而另一个却没有这个注解。按理说,@ConditionalOnMissingBean 标记的方法应该会被SpringBoot忽略掉,为什么会不生效呢?
具体的包和代码结构如下:
一共有两个模块,boot模块表示项目的启动模块,而thirdparty模块则表示引用的组件库,可能是自研的也可能是第三方提供的。在这两个模块中,有如下的类:
下面分别对boot模块和thirdparty模块的类进行介绍。
对于boot模块,ProjectMinimalsApplication 类表示SpringBoot的启动类,JacksonConfig类表示在boot模块中Jackson配置类,其代码如下:
java复制代码
// com.peng.project.ProjectMinimalsApplication
@SpringBootApplication(scanBasePackageClasses = ProjectMinimalsApplication.class, scanBasePackages = "com.peng.project.thirdparty")
public class ProjectMinimalsApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(ProjectMinimalsApplication.class)
.beanNameGenerator(FullyQualifiedAnnotationBeanNameGenerator.INSTANCE)
.run(args);
}
}
// com.peng.project.JacksonConfig
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer initJackson() {
return builder -> {
};
}
}
对于thirdparty模块,FrameworkAutoConfiguration 类是组件库的自动配置类,JacksonConfig类是组件库中Jackson配置类,其代码如下:
java复制代码
// com.peng.project.thirdparty.FrameworkAutoConfiguration
@Import({
JacksonConfig.class,
})
public class FrameworkAutoConfiguration {
}
// com.peng.project.thirdparty.JacksonConfig
@Configuration
public class JacksonConfig {
@Bean
@ConditionalOnMissingBean
public Jackson2ObjectMapperBuilderCustomizer initJackson() {
return builder -> {
};
}
}
FrameworkAutoConfiguration 是自动配置类,需要在thirdparty模块中配置META-INF/spring.factories 文件:
java复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.peng.project.thirdparty.FrameworkAutoConfiguration
代码就这么多,却在启动项目的时候,出现了上述错误。而且,当在META-INF/spring.factories文件增加thirdparty中的 JacksonConfig 配置时,项目却能正常启动了:
java复制代码
// 没有配置JacksonConfig,项目启动出错
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.peng.project.thirdparty.FrameworkAutoConfiguration
// 配置了JacksonConfig,项目正常启动
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.peng.project.thirdparty.JacksonConfig,\
com.peng.project.thirdparty.FrameworkAutoConfiguration
这非常奇怪。thirdparty模块中的JacksonConfig 类明明已经被 @Configuration 注解标记了,为什么启动的时候 @ConditionalOnMissingBean 没有生效,而把 JacksonConfig 类加到自动配置类中的时候,却能正常启动?为解答这个问题,要先了解SpringBoot中被 @Configuration 注解的类 是如何被SpringBoot识别到的,也就是组件扫描和自动配置流程。
理解整体流程
熟悉SpringBoot的对Bean的生命周期都会有所了解。我们写的被@Component标记的或其派生注解(比如@Service、@Configuration等)标记的类,是怎么在SpringBoot运行时变为Bean对象的呢?如图所示:
在进入Bean的生命周期之前,需要找到这些被注解的类,将这些类定义解析成 BeanDefinition ,再根据BeanDefinition 信息构造出Bean对象。而将配置文件或配置类变为BeanDefinition 的方式有两种:组件扫描@ComponentScan 和自动配置 @EnableAutoConfiguration。
那这两种方式是如何把配置类解析成BeanDefinition的呢?这还需要经过一些步骤,如图所示: