Spring Boot自动装配全流程:从@SpringBootApplication到Bean注册
Spring Boot自动装配全流程:从@SpringBootApplication到Bean注册
适读人群:想彻底理解Spring Boot启动过程、不再靠"记结论"的Java开发者 | 阅读时长:约20分钟
开篇故事
有次做 code review,一个同学把一堆配置类放在了 com.company.config 包,但项目启动类在 com.company,结果扫描正常。另一个同学把一个 Starter 的配置类放在了 com.thirdparty.xxx 包,用 @ComponentScan 扫描怎么都扫不到,最后发现需要放 META-INF/spring.factories。
这两个场景让同学很困惑:为什么同样是加 @Configuration,一个直接扫到,一个需要 SPI 文件?
这个问题的答案就藏在 Spring Boot 自动装配的全流程里。今天从 @SpringBootApplication 出发,把整个装配过程走一遍。
一、@SpringBootApplication 是什么
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 三个核心注解的组合
@SpringBootConfiguration // 等价于 @Configuration
@EnableAutoConfiguration // 开启自动装配
@ComponentScan // 扫描当前包及子包
public @interface SpringBootApplication {
// ...
}一个注解干了三件事:
@SpringBootConfiguration:把启动类本身注册为配置类@EnableAutoConfiguration:触发自动装配机制(核心)@ComponentScan:扫描启动类所在包及子包里的组件
二、全流程架构图
三、核心源码追踪
3.1 @EnableAutoConfiguration 的处理
// @EnableAutoConfiguration 上有这个注解
@Import(AutoConfigurationImportSelector.class)
// AutoConfigurationImportSelector 是一个 DeferredImportSelector
// 它在所有 @Configuration 类处理完毕后,最后才执行(defer = 延迟)
// 这样可以确保用户自定义的Bean优先注册,自动配置的Bean用@ConditionalOnMissingBean判断是否需要注册
public class AutoConfigurationImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 实际上用的是 getAutoConfigurationEntry,这里简化
return getAutoConfigurationEntry(annotationMetadata).getConfigurations().toArray(new String[0]);
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata metadata) {
AnnotationAttributes attributes = getAttributes(metadata);
// 1. 从 spring.factories 加载所有候选类
List<String> configurations = getCandidateConfigurations(metadata, attributes);
// 2. 去重
configurations = removeDuplicates(configurations);
// 3. 处理 @SpringBootApplication(exclude = ...)
Set<String> exclusions = getExclusions(metadata, attributes);
configurations.removeAll(exclusions);
// 4. 过滤(@Conditional 条件评估)
configurations = getConfigurationClassFilter().filter(configurations);
return new AutoConfigurationEntry(configurations, exclusions);
}
}3.2 ConfigurationClassPostProcessor 的工作
ConfigurationClassPostProcessor 是 Spring 中最重要的 BeanDefinitionRegistryPostProcessor,负责处理所有 @Configuration 类:
// 处理顺序(重要!)
// 1. @PropertySource:加载配置文件
// 2. @ComponentScan:扫描并注册Bean定义
// 3. @Import:处理@Import注解(包括AutoConfigurationImportSelector)
// 4. @ImportResource:加载XML配置
// 5. @Bean 方法:注册方法返回值为Bean
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 从已注册的Bean定义里找出所有@Configuration类
Set<BeanDefinitionHolder> candidates = getConfigurationClasses(registry);
// 解析所有@Configuration类
ConfigurationClassParser parser = new ConfigurationClassParser(...);
parser.parse(candidates);
// 把解析结果(Bean定义)注册到BeanFactory
ConfigurationClassBeanDefinitionReader reader = new ConfigurationClassBeanDefinitionReader(...);
reader.loadBeanDefinitions(parser.getConfigurationClasses());
}
}四、完整代码演示
4.1 手动触发自动装配:理解加载顺序
package com.laozhang.boot.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
/**
* 通过启动日志验证自动装配流程
*/
@SpringBootApplication
public class AutoConfigDemoApplication {
public static void main(String[] args) {
// 开启调试模式:输出自动配置报告
System.setProperty("debug", "true");
ConfigurableApplicationContext ctx = SpringApplication.run(AutoConfigDemoApplication.class, args);
// 验证Bean是否注册成功
String[] beanNames = ctx.getBeanDefinitionNames();
System.out.println("已注册的Bean数量: " + beanNames.length);
// 查看特定Bean是否存在
boolean hasRedisTemplate = ctx.containsBean("redisTemplate");
System.out.println("redisTemplate是否存在: " + hasRedisTemplate);
}
}启动时加 --debug 参数,会输出自动配置报告:
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches: ← 条件满足,被加载的自动配置
-----------------
RedisAutoConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.data.redis.core.RedisTemplate' (OnClassCondition)
Negative matches: ← 条件不满足,未加载的自动配置
-----------------
ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)4.2 自定义 ApplicationContextInitializer
package com.laozhang.boot.demo;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
/**
* ApplicationContext 初始化器
* 在 ApplicationContext 刷新之前执行,可以修改环境变量、注册Bean等
*/
public class CustomContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("[Initializer] ApplicationContext 初始化阶段");
// 可以在这里:
// 1. 动态添加配置属性
// 2. 注册BeanFactoryPostProcessor
// 3. 设置active profiles
}
}注册方式:META-INF/spring.factories
org.springframework.context.ApplicationContextInitializer=\
com.laozhang.boot.demo.CustomContextInitializer4.3 理解 @ComponentScan 的扫描范围
// 场景1:默认扫描(扫描启动类所在包及子包)
@SpringBootApplication
// com.laozhang.demo.Application → 扫描 com.laozhang.demo 及所有子包
// 场景2:自定义扫描包
@SpringBootApplication(scanBasePackages = {"com.laozhang.demo", "com.laozhang.shared"})
// 场景3:排除某些类
@SpringBootApplication(
exclude = {DataSourceAutoConfiguration.class} // 排除数据源自动配置
)
// 场景4:第三方包里的配置类(不在扫描范围内),必须用 @Import
@SpringBootApplication
@Import(ThirdPartyConfig.class) // 显式导入4.4 自定义 Banner 和 SpringApplication 配置
package com.laozhang.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(MainApplication.class)
// 关闭 banner
.bannerMode(Banner.Mode.OFF)
// 添加自定义初始化器
.initializers(new CustomContextInitializer())
// 添加应用监听器
.listeners(new CustomApplicationListener())
// 设置额外属性
.properties("server.port=8080")
.run(args);
}
}五、踩坑实录
坑1:把启动类放在非根包,@ComponentScan 扫描不到子模块
症状:子模块 common 里的 @Service Bean 注入时报 NoSuchBeanDefinitionException。
根因:@ComponentScan 默认只扫描启动类所在包(不是项目根目录,是 Java 包路径),如果子模块的包路径不在这个范围内,扫描不到。
解决:
// 方式1:把启动类放在最顶层包
package com.laozhang; // 所有子包都能被扫描
// 方式2:显式指定扫描包
@ComponentScan(basePackages = {"com.laozhang.service", "com.laozhang.common"})
// 方式3:对于公共模块,做成Starter(通过SPI注册配置类)坑2:@SpringBootApplication 的 exclude 不生效
症状:设置了 exclude = DataSourceAutoConfiguration.class,启动还是报数据源相关错误。
根因:exclude 排除的是类对象,但 Spring 先通过全限定类名加载,如果类名写错或者用了错误的类(比如版本不对),排除无效。
正确写法:
// 方式1:用类对象(推荐,有编译期检查)
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
// 方式2:用全限定类名(当引入的类在classpath里找不到时用这个)
@SpringBootApplication(excludeName = {"org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration"})
// 方式3:在 application.yml 里配置
// spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration坑3:自动配置类里的 Bean 覆盖不了
症状:自定义了一个 DataSource Bean,但 Spring Boot 自动配置的 DataSource 覆盖了它。
根因:自动配置类本来应该用 @ConditionalOnMissingBean,但如果 Boot 版本有 Bug,或者自定义 Bean 的注册时机晚于自动配置类,就会出现覆盖。
排查:加 --debug 参数看自动配置报告,确认自定义 Bean 的注册时机。
解决:给自定义 Bean 加 @Primary,或者用 spring.main.allow-bean-definition-overriding=true(Spring Boot 2.1+ 默认禁止覆盖)。
坑4:DeferredImportSelector 执行顺序的影响
症状:两个 Starter 都定义了同名 Bean,容器里只有一个,且不是想要的那个。
根因:AutoConfigurationImportSelector 是 DeferredImportSelector,延迟到所有普通 @Configuration 处理完后才执行,并且多个 AutoConfiguration 之间的顺序由 @AutoConfigureOrder、@AutoConfigureBefore、@AutoConfigureAfter 控制。
// 在自动配置类上控制顺序
@AutoConfiguration(after = {DataSourceAutoConfiguration.class})
public class MyAutoConfiguration {
// 保证在 DataSourceAutoConfiguration 之后注册
}五、总结与延伸
Spring Boot 自动装配的关键链路:
@SpringBootApplication组合了三个注解@EnableAutoConfiguration触发AutoConfigurationImportSelectorAutoConfigurationImportSelector是DeferredImportSelector,最后执行- 通过
SpringFactoriesLoader加载spring.factories/AutoConfiguration.imports @Conditional注解过滤掉不满足条件的配置类- 有效的配置类注册到
BeanFactory,最终实例化
理解了这条链路,"零配置"就不再是魔法,而是有迹可循的工程实践。
