Spring Boot自动装配面试精讲:从@SpringBootApplication到Bean注册的10个步骤
Spring Boot自动装配面试精讲:从@SpringBootApplication到Bean注册的10个步骤
适读人群:Java后端开发 | 难度:★★★★☆ | 出现频率:极高
开篇故事
我刚开始用Spring Boot的时候,觉得它简直是魔法。只需要一个@SpringBootApplication注解,什么数据源、Redis客户端、MVC配置,通通自动好了。完全不需要像以前写Spring XML那样,每个Bean都要手动配置。
但魔法背后是原理。有次面试,面试官问:"你知道Spring Boot是怎么实现自动装配的吗?就是那个spring.factories文件,你能讲讲它的工作流程吗?"
我那次答得不够好,只说了个大概。回去研究了一周,把源码翻了一遍,才真正搞清楚了整个链路。今天把这10个步骤完整讲给你。
一、高频考点拆解
Spring Boot自动装配这道题,面试官考察三个层次:
第一层:知道结论——Spring Boot通过读取spring.factories(或Spring Boot 3的AutoConfiguration.imports)文件,自动注册第三方的AutoConfiguration类
第二层:知道流程——@SpringBootApplication → @EnableAutoConfiguration → AutoConfigurationImportSelector → 读文件 → 过滤条件 → 注册Bean
第三层:知道条件注解——@ConditionalOnClass、@ConditionalOnMissingBean等条件注解如何控制Bean的注册
二、深度原理分析
2.1 @SpringBootApplication的三层结构
@SpringBootConfiguration // 等同于@Configuration,声明这是配置类
@EnableAutoConfiguration // 开启自动装配(核心!)
@ComponentScan(excludeFilters = { // 组件扫描,排除特定类型
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication { ... }@EnableAutoConfiguration是触发自动装配的核心注解:
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) // 关键:导入这个选择器
public @interface EnableAutoConfiguration { ... }2.2 自动装配的完整10步流程
2.3 核心条件注解
这些条件注解控制Bean的注册:
@ConditionalOnClass(DataSource.class) // classpath中有DataSource类才注册
@ConditionalOnMissingClass("org.xxx.Foo") // classpath中没有Foo类才注册
@ConditionalOnBean(DataSource.class) // 容器中有DataSource Bean才注册
@ConditionalOnMissingBean(DataSource.class) // 容器中没有DataSource Bean才注册(最常用!)
@ConditionalOnProperty(prefix="spring.datasource", name="url") // 有特定配置项才注册
@ConditionalOnWebApplication // 是Web应用才注册
@ConditionalOnNotWebApplication // 不是Web应用才注册@ConditionalOnMissingBean是自动装配的"非侵入性"保证:如果用户已经自定义了某个Bean,框架的默认实现就不注册,用户的配置优先。
三、标准答案 + 代码验证
3.1 手写一个Starter
理解自动装配最好的方式是自己写一个Starter:
// 1. 定义配置属性类
@ConfigurationProperties(prefix = "my.greeting")
public class GreetingProperties {
private String message = "Hello"; // 默认值
private String name = "World";
// getters/setters
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
// 2. 定义业务Service
public class GreetingService {
private final GreetingProperties properties;
public GreetingService(GreetingProperties properties) {
this.properties = properties;
}
public String greet() {
return properties.getMessage() + ", " + properties.getName() + "!";
}
}
// 3. 定义AutoConfiguration
@Configuration
@EnableConfigurationProperties(GreetingProperties.class) // 绑定配置
@ConditionalOnClass(GreetingService.class) // classpath有这个类才生效
public class GreetingAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 用户没有自定义GreetingService才注册默认的
public GreetingService greetingService(GreetingProperties properties) {
return new GreetingService(properties);
}
}
// 4. 注册到spring.factories
// 文件位置:src/main/resources/META-INF/spring.factories
// 内容:
// org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
// com.example.starter.GreetingAutoConfiguration3.2 验证自动装配效果
// 应用程序代码
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
GreetingService service = ctx.getBean(GreetingService.class);
System.out.println(service.greet()); // Hello, World!
}
}
// application.properties配置
// my.greeting.message=你好
// my.greeting.name=张三
// 配置后输出:你好, 张三!// 测试ConditionalOnMissingBean:用户自定义时,不用默认的
@Configuration
public class MyConfig {
// 用户自定义了GreetingService,AutoConfiguration中的默认Bean不会注册
@Bean
public GreetingService myGreetingService() {
return new GreetingService(new GreetingProperties() {
{ setMessage("こんにちは"); setName("世界"); }
});
}
}
// 输出:こんにちは, 世界!3.3 查看自动装配的调试信息
// application.properties 中添加:
// debug=true
// Spring Boot会输出AutoConfiguration的Report,包括:
// Positive matches(生效的AutoConfiguration)
// Negative matches(未生效的AutoConfiguration及原因)
// Exclusions(被排除的)
// 示例输出(debug=true时):
// ============================
// CONDITIONS EVALUATION REPORT
// ============================
// Positive matches:
// -----------------
// DataSourceAutoConfiguration matched:
// - @ConditionalOnClass found required classes 'javax.sql.DataSource'
//
// Negative matches:
// -----------------
// ActiveMQAutoConfiguration:
// Did not match:
// - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory'四、面试官追问
追问1:Spring Boot 3.x中spring.factories被废弃了,用什么替代?
我的回答:Spring Boot 3.x(基于Spring Framework 6.x)中,AutoConfiguration的注册方式改为了META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件。与spring.factories不同,这个新文件只列AutoConfiguration类名(每行一个),不需要键值对格式,也不需要EnableAutoConfiguration的key。旧的spring.factories中其他类型的注册(如ApplicationListener、ApplicationContextInitializer)继续在spring.factories中。这个改变的目的是提高性能(AOT编译时更容易处理)和简化格式。
追问2:自动装配的顺序是如何保证的?
我的回答:AutoConfiguration类可以用@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder注解来声明顺序依赖。比如@AutoConfigureAfter(DataSourceAutoConfiguration.class)表示这个AutoConfiguration要在DataSource配置之后处理,确保DataSource Bean已经在容器中了。如果没有显式声明顺序,Spring会根据条件注解的依赖关系推断一个合理的顺序。
追问3:@Import注解有哪几种用法?
我的回答:@Import有三种用法。第一,直接导入配置类(@Configuration类),相当于把另一个配置类的内容合并进来。第二,导入ImportSelector实现类,通过selectImports()方法返回需要注册的类名数组,AutoConfigurationImportSelector就是这种。第三,导入ImportBeanDefinitionRegistrar实现类,通过registerBeanDefinitions()方法直接向BeanDefinitionRegistry注册BeanDefinition,这是最底层的注册方式,可以动态生成和注册Bean。
五、同类题目举一反三
Spring Boot和Spring的区别是什么?
Spring是基础框架,提供IoC、AOP、事务管理等核心功能,但配置繁琐(需要大量XML或Java Config)。Spring Boot基于Spring,通过自动装配大幅简化配置,通过内嵌Tomcat/Jetty避免了部署到外部容器,通过Starter简化了依赖管理,通过Actuator提供了开箱即用的监控端点。Spring Boot没有发明新的技术,它是Spring生态的"约定优于配置"实践,让开发者聚焦业务逻辑,而不是框架配置。
六、踩坑实录
坑一:自定义AutoConfiguration没有注册到spring.factories,以为会自动发现
刚写完第一个Starter,在类上加了@Configuration,以为Spring Boot会自动扫描到。结果Bean一个都没注入。排查了半天才想起来:自动装配必须在spring.factories(或Spring Boot 3的imports文件)中显式注册,Spring Boot不会扫描第三方jar包里的@Configuration类(那是组件扫描的工作,范围只限于本应用的包)。
坑二:ConditionalOnMissingBean的判断时机导致Bean被意外替换
有次我定义了一个DataSource Bean,但还是被Spring Boot的DataSourceAutoConfiguration覆盖了。排查后发现:@ConditionalOnMissingBean的判断依赖Bean的注册顺序,我的DataSource Bean定义在一个@Configuration类里,但这个配置类的加载顺序晚于AutoConfiguration,导致判断时我的Bean还没注册,AutoConfiguration判断"MissingBean"成立,注册了默认的DataSource,后来我的DataSource再注册,就有了两个DataSource。
修复方案:自定义的@Configuration类上加@Primary,或者用@AutoConfigureBefore调整顺序,或者直接在application.properties中配置数据源(这样AutoConfiguration能感知到)。
坑三:Starter的依赖传递污染
写了一个内部Starter,引入了很多依赖,结果所有引用这个Starter的项目都被迫引入了这些依赖,导致classpath污染,某些类发现了不期望的AutoConfiguration。规范写法是:Starter的pom只引入必要的核心依赖,可选的功能依赖用optional标记,或者提供单独的Starter变体(如spring-boot-starter-web和spring-boot-starter-webflux分开)。
七、总结
Spring Boot自动装配的10步流程:
@SpringBootApplication包含@EnableAutoConfiguration@EnableAutoConfiguration通过@Import引入AutoConfigurationImportSelector- Spring处理
@Import时调用selectImports() - 读取
META-INF/spring.factories中的AutoConfiguration列表 - 加载所有候选AutoConfiguration类名
- 去重和排除(spring.autoconfigure.exclude配置)
- 用OnClassCondition、OnBeanCondition等过滤不满足条件的
- 剩余满足条件的AutoConfiguration类被注册为配置类
- Spring解析这些配置类
@Bean方法执行,Bean被注册到容器
理解了这个流程,再加上条件注解的工作原理,Spring Boot"零配置"的魔法就完全揭开了。
