Spring Boot 源码解析(二):@SpringBootApplication 到底干了什么?
大家好,我是老 J。
上期我们讲了 Spring Boot 的整体启动流程,知道了 SpringApplication.run() 背后发生了什么。
这期来拆解 Spring Boot 最核心的一个注解——@SpringBootApplication。
这也是面试高频题:“@SpringBootApplication 注解有什么作用?它由哪些注解组成?”
一、一个注解,三个核心
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration// ① 标明这是一个配置类@EnableAutoConfiguration// ② 开启自动配置(核心!)@ComponentScan( // ③ 组件扫描 excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public@interface SpringBootApplication {// ...}
三个核心注解:
|
|
|
@SpringBootConfiguration |
@Configuration,标明这是一个配置类 |
@EnableAutoConfiguration |
开启自动配置
|
@ComponentScan |
|
二、逐层拆解
① @SpringBootConfiguration
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Configuration// 实际上就是个 @Configurationpublic@interface SpringBootConfiguration {@AliasFor(annotation = Configuration.class)booleanproxyBeanMethods()defaulttrue;}
作用: 标明这是一个 Spring 配置类,等价于 @Configuration。
面试回答: “@SpringBootConfiguration 其实就是 @Configuration 的包装,让 Spring Boot 启动类同时也作为一个配置类,可以在里面定义 @Bean。”
② @ComponentScan
@ComponentScan( excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
作用: 扫描并注册 Bean。
默认扫描范围: 启动类所在的包及其所有子包。
// 项目结构com.example.blog├── Application.java // 启动类(@SpringBootApplication)├── controller // ✅ 会被扫描├── service // ✅ 会被扫描├── repository // ✅ 会被扫描└── config // ✅ 会被扫描// 外部包com.example.external // ❌ 不会被扫描(需要额外配置@ComponentScan)
自定义扫描:
@SpringBootApplication(scanBasePackages = {"com.example.blog", "com.example.common"})publicclassApplication {publicstaticvoidmain(String[] args) { SpringApplication.run(Application.class, args); }}
排除扫描:
@SpringBootApplication@ComponentScan(excludeFilters = { @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.example.test.*")})publicclassApplication {// ...}
③ @EnableAutoConfiguration(最核心)
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage// 自动配置包@Import(AutoConfigurationImportSelector.class)// 导入自动配置public@interface EnableAutoConfiguration {StringENABLED_OVERRIDE_PROPERTY="spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {};}
两个核心部分:
1. @AutoConfigurationPackage
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Import(AutoConfigurationPackages.Registrar.class)// 注册包名public@interface AutoConfigurationPackage {}
作用: 记录启动类所在的包名,供后续自动配置使用(比如 JPA 实体扫描)。
2. @Import(AutoConfigurationImportSelector.class)
这是自动配置的核心!AutoConfigurationImportSelector 负责加载所有自动配置类。
publicclassAutoConfigurationImportSelectorimplementsDeferredImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {// 1. 检查是否开启自动配置if (!isEnabled(annotationMetadata)) {return NO_IMPORTS; }// 2. 加载自动配置元数据AutoConfigurationEntryautoConfigurationEntry= getAutoConfigurationEntry(annotationMetadata);// 3. 返回要导入的配置类名数组return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }protected AutoConfigurationEntry getAutoConfigurationEntry( AnnotationMetadata annotationMetadata) {// 加载 META-INF/spring.factories 中所有 EnableAutoConfiguration 配置 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);// 过滤排除项 configurations = removeDuplicates(configurations); configurations = filter(configurations, autoConfigurationMetadata);returnnewAutoConfigurationEntry(configurations, exclusions); }}
加载流程:
@EnableAutoConfiguration │ ▼AutoConfigurationImportSelector │ ▼加载 META-INF/spring.factories │ ▼key = org.springframework.boot.autoconfigure.EnableAutoConfiguration │ ▼得到 100+ 自动配置类(如 DataSourceAutoConfiguration) │ ▼条件注解(@Conditional)过滤 │ ▼最终生效的自动配置
三、自动配置原理
1. spring.factories 文件
以 spring-boot-autoconfigure 包为例:
# META-INF/spring.factories(部分)org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
2. 条件注解(@Conditional)
自动配置类不是无条件生效的,而是通过条件注解按需加载。
|
|
|
@ConditionalOnClass |
|
@ConditionalOnMissingClass |
|
@ConditionalOnBean |
|
@ConditionalOnMissingBean |
|
@ConditionalOnProperty |
|
@ConditionalOnWebApplication |
|
3. 看一个具体的自动配置类
// DataSourceAutoConfiguration 部分代码@Configuration(proxyBeanMethods = false)@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})@EnableConfigurationProperties(DataSourceProperties.class)@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})publicclassDataSourceAutoConfiguration {@Configuration(proxyBeanMethods = false)@ConditionalOnMissingBean(DataSource.class)@ConditionalOnProperty(name = "spring.datasource.type")staticclassDataSourceConfiguration {// 创建 DataSource Bean }@Configuration(proxyBeanMethods = false)@ConditionalOnClass(HikariDataSource.class)@ConditionalOnMissingBean(DataSource.class)@ConditionalOnProperty( name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true )staticclassHikari {// HikariCP 连接池配置 }}
解读:
-
• @ConditionalOnClass(DataSource.class):只有 classpath 中有 DataSource 才加载 -
• @ConditionalOnMissingBean(DataSource.class):用户没有自定义 DataSource 时才自动配置 -
• @ConditionalOnProperty(name = "spring.datasource.type"):配置了 type 才生效
4. 自动配置执行流程图
启动类上有 @SpringBootApplication │ ▼ @EnableAutoConfiguration │ ▼AutoConfigurationImportSelector │ ▼加载 spring.factories 中 100+ 自动配置类 │ ▼逐个加载,遇到 @Conditional 条件判断 │ ┌───────┴───────┐ ▼ ▼ 条件满足 条件不满足 │ │ ▼ ▼ 加载配置 跳过配置
四、常见问题与面试回答
问题1:为什么要用 @SpringBootApplication,而不是分别用三个注解?
面试回答: “@SpringBootApplication 是一个组合注解,将 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 三个注解整合在一起,简化了配置。当然,如果有特殊需求,也可以分开使用。”
问题2:自动配置的原理是什么?
面试回答:
Spring Boot 的自动配置基于
@EnableAutoConfiguration注解。
1. 启动时, AutoConfigurationImportSelector会加载META-INF/spring.factories文件中 key 为EnableAutoConfiguration的所有配置类。
2. 这些配置类通过 @Conditional系列注解按条件加载。比如@ConditionalOnClass要求 classpath 中有特定类才加载,@ConditionalOnMissingBean要求用户没有自定义 Bean 才加载。
3. 这样既实现了开箱即用,又保证了用户可以通过自定义配置覆盖默认行为。
问题3:如何排除某个自动配置?
# application.ymlspring:autoconfigure:exclude:-org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)publicclassApplication {// ...}
问题4:为什么默认扫描的是启动类所在包?
面试回答: “因为 @ComponentScan 默认扫描的是被标注类所在的包。@SpringBootApplication 标在启动类上,所以默认扫描启动类所在包及其子包。这个行为可以通过 scanBasePackages 属性修改。”
五、一张图记住核心逻辑
@SpringBootApplication │ ├── @SpringBootConfiguration → @Configuration → 启动类也是配置类 │ ├── @ComponentScan → 扫描启动类所在包及子包 → 注册 @Service/@Controller │ └── @EnableAutoConfiguration │ ├── @AutoConfigurationPackage → 记录包名 │ └── @Import(AutoConfigurationImportSelector) │ ▼ 加载 META-INF/spring.factories │ ▼ 100+ 自动配置类 │ ▼ @Conditional 条件过滤 │ ▼ 最终生效的自动配置
六、下期预告
Spring Boot 源码解析(三):自动配置源码深度剖析
-
• AutoConfigurationImportSelector 源码分析 -
• 条件注解的实现原理 -
• 如何自定义 Starter -
• 面试实战
七、互动时间
你在使用 Spring Boot 自动配置时遇到过什么问题?
-
• 自动配置不生效? -
• 自定义配置被覆盖? -
• 条件注解不生效?
评论区聊聊,下期安排你关心的问题。
我是老 J,下期见。
夜雨聆风