SpringBoot自动装配原理:从源码到实战,彻底搞懂
一、什么是自动装配?
@SpringBootApplication // 就这一个注解public class MyApplication {publicstaticvoidmain(String[] args) {SpringApplication.run(MyApplication.class, args);}}
就这么简单,SpringBoot就给我们准备好了:
-
内嵌的Tomcat服务器
-
数据源配置(如果引入了数据库依赖)
-
Redis模板(如果引入了Redis)
-
各种开箱即用的功能
自动装配的核心目标:根据classpath下的依赖,自动配置好相应的Bean,让我们无需写繁琐的配置类。
二、入口:@SpringBootApplication
点进这个注解看看:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration // 重点!自动装配的核心注解@ComponentScan // 组件扫描public @interface SpringBootApplication {// ...}
三个核心注解:
-
@SpringBootConfiguration:其实就是@Configuration,表示这是一个配置类
-
@ComponentScan:组件扫描,默认扫描当前包及其子包
-
@EnableAutoConfiguration:自动装配的核心,重点中的重点!
三、核心:@EnableAutoConfiguration
点进这个注解:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage // 1. 自动配置包@Import(AutoConfigurationImportSelector.class) // 2. 导入选择器public @interface EnableAutoConfiguration {// ...}
两个关键点:
-
@AutoConfigurationPackage:自动配置包,稍后说
-
@Import(AutoConfigurationImportSelector.class):导入选择器,核心逻辑在这里!
四、核心机制:SpringFactoriesLoader
4.1 先看最关键的配置文件
SpringBoot在所有starter的jar包里,都有一个固定的文件:
META-INF/spring.factories
随便找一个,比如spring-boot-autoconfigure里的:
# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\... 还有上百个配置类
关键点:这个文件里罗列了所有可能的自动配置类,但并不是全部生效,而是根据条件判断。
4.2 AutoConfigurationImportSelector怎么工作?
public class AutoConfigurationImportSelector implements DeferredImportSelector {// 核心方法:选择导入哪些配置类public String[] selectImports(AnnotationMetadata annotationMetadata) {// 1. 获取配置信息AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(beanClassLoader);// 2. 获取所有候选配置(从spring.factories读取)List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);// 3. 去重configurations = removeDuplicates(configurations);// 4. 排序configurations = sort(configurations, autoConfigurationMetadata);// 5. 按条件过滤(关键!)configurations = filter(configurations, autoConfigurationMetadata);return StringUtils.toStringArray(configurations);}// 从spring.factories读取候选配置protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());return configurations;}}
核心逻辑:
-
从所有
META-INF/spring.factories中读取EnableAutoConfiguration对应的值 -
得到上百个候选配置类
-
根据
@Conditional条件进行过滤 -
返回最终生效的配置类
五、条件装配:@Conditional
自动配置不是全盘接收,而是有条件的。SpringBoot提供了丰富的条件注解:
// 只有存在DataSource类时才生效@ConditionalOnClass(DataSource.class)// 只有不存在DataSource时才生效@ConditionalOnMissingBean(DataSource.class)// 只有当配置了某个属性时才生效@ConditionalOnProperty(name = "spring.datasource.enabled", havingValue = "true")// 只有Web应用时才生效@ConditionalOnWebApplication// 只有某个Bean存在时才生效@ConditionalOnBean(JdbcTemplate.class)// 只有某个Bean不存在时才生效@ConditionalOnMissingBean(JdbcTemplate.class)
5.1 看一个实际例子:DataSourceAutoConfiguration
@Configuration@ConditionalOnClass(DataSource.class) // 必须有DataSource类@EnableConfigurationProperties(DataSourceProperties.class) // 绑定配置public class DataSourceAutoConfiguration {@Configuration@ConditionalOnMissingBean(DataSource.class) // 用户没有自己定义DataSourcestatic class PooledDataSourceConfiguration {@Bean@ConditionalOnMissingBean // 没有自定义时才创建public DataSource dataSource() {// 创建数据源(HikariCP、Tomcat JDBC等)return createDataSource();}}}
判断逻辑:
-
先看
DataSource.class是否存在(有数据库驱动才会存在) -
再看用户有没有自己定义
DataSourceBean -
都没有,才创建默认的DataSource
六、完整流程源码追踪
6.1 从@SpringBootApplication开始
// 启动类@SpringBootApplicationpublic class App {publicstaticvoidmain(String[] args) {// 1. 创建SpringApplicationSpringApplication.run(App.class, args);}}
6.2 进入refresh方法
// AbstractApplicationContext.refresh()publicvoidrefresh() throws BeansException {// ...// 调用所有BeanFactoryPostProcessorinvokeBeanFactoryPostProcessors(beanFactory);// ...}
6.3 ConfigurationClassPostProcessor处理配置类
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {// 解析所有配置类processConfigBeanDefinitions(registry);}private void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {// 1. 找出所有配置类List<BeanDefinitionHolder> configCandidates = findConfigCandidates(registry);// 2. 创建配置类解析器ConfigurationClassParser parser = new ConfigurationClassParser();// 3. 解析配置类(关键!会处理@Import)parser.parse(candidates);// 4. 注册解析出的Bean定义this.reader.loadBeanDefinitions(configClasses);}}
6.4 ConfigurationClassParser解析@Import
class ConfigurationClassParser {protected void processConfigurationClass(ConfigurationClass configClass) {// 递归处理配置类doProcessConfigurationClass(configClass, sourceClass);}protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) {// 处理@Import注解processImports(configClass, sourceClass, getImports(sourceClass), true);return null;}private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,Collection<SourceClass> importCandidates, boolean checkForCircularImports) {for (SourceClass candidate : importCandidates) {// 判断@Import的是什么类型if (candidate.isAssignable(ImportSelector.class)) {// 是ImportSelector:执行selectImportsImportSelector selector = (ImportSelector) loadClass(candidate).newInstance();String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());// 递归处理返回的类processImports(configClass, currentSourceClass, asSourceClasses(importClassNames), false);}else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {// 是ImportBeanDefinitionRegistrar:暂存,稍后处理configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());}else {// 是普通配置类:直接注册processConfigurationClass(candidate.asConfigClass(configClass));}}}}
6.5 AutoConfigurationImportSelector执行selectImports
public class AutoConfigurationImportSelector implements DeferredImportSelector {public String[] selectImports(AnnotationMetadata annotationMetadata) {// 1. 获取所有候选配置List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);// 2. 去重configurations = removeDuplicates(configurations);// 3. 按条件过滤configurations = filter(configurations, autoConfigurationMetadata);return StringUtils.toStringArray(configurations);}protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {// 从spring.factories加载所有EnableAutoConfiguration对应的类List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());// 断言:如果找不到配置类,抛出异常Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories");return configurations;}private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {// 创建过滤器AutoConfigurationImportFilter[] filters = getAutoConfigurationImportFilters();// 执行过滤(这里会执行所有@Conditional判断)boolean[] match = filter.match(candidates, autoConfigurationMetadata);// 返回匹配的配置类return result;}}
七、图解自动装配全流程
启动类 (@SpringBootApplication)↓@EnableAutoConfiguration↓@Import(AutoConfigurationImportSelector.class)↓AutoConfigurationImportSelector.selectImports()↓SpringFactoriesLoader.loadFactoryNames() ← 读取 META-INF/spring.factories↓获取所有候选配置类(上百个)↓┌──────────────────────────────────────┐│ 条件过滤(@Conditional系列) ││ ├─ @ConditionalOnClass ││ ├─ @ConditionalOnMissingBean ││ ├─ @ConditionalOnProperty ││ └─ @ConditionalOnWebApplication │└──────────────────────────────────────┘↓只返回符合条件的配置类(比如有DataSource类才返回DataSource配置)↓Spring容器注册这些配置类↓配置类中的@Bean方法执行↓自动配置的Bean就绪!
八、实战:手写一个自动配置
为了加深理解,我们来写一个简单的自动配置:
8.1 创建自定义Starter
// 1. 定义属性类(用于绑定配置)@ConfigurationProperties(prefix = "hello")public class HelloProperties {private String prefix = "Hello";private String suffix = "!";// getters/setters...}// 2. 定义服务类public class HelloService {private String prefix;private String suffix;public String sayHello(String name) {return prefix + " " + name + suffix;}}// 3. 定义自动配置类@Configuration@ConditionalOnClass(HelloService.class) // 有HelloService才生效@EnableConfigurationProperties(HelloProperties.class) // 启用属性绑定public class HelloAutoConfiguration {@Bean@ConditionalOnMissingBean(HelloService.class) // 用户没定义才创建public HelloService helloService(HelloProperties properties) {HelloService service = new HelloService();service.setPrefix(properties.getPrefix());service.setSuffix(properties.getSuffix());return service;}}
8.2 创建spring.factories
在resources/META-INF/spring.factories中:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.example.HelloAutoConfiguration
8.3 使用自定义Starter
// 引入依赖后,直接使用@SpringBootApplicationpublic class App {publicstaticvoidmain(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(App.class, args);// 直接从容器获取HelloServiceHelloService helloService = context.getBean(HelloService.class);System.out.println(helloService.sayHello("World")); // 输出:Hello World!}}// 或者在application.yml中自定义hello:prefix: "Hi"suffix: "~"// 输出:Hi World~
九、面试话术模板
面试官问:”SpringBoot的自动装配原理是什么?”
回答思路(层层递进):
第一层(宏观):
“SpringBoot的自动装配核心是@EnableAutoConfiguration注解,它通过@Import导入AutoConfigurationImportSelector,后者会读取META-INF/spring.factories中的配置类,然后根据条件注解进行过滤,最终只装配需要的Bean。”
第二层(细节):
“具体来说,SpringBoot在启动时会调用AutoConfigurationImportSelector.selectImports()方法,这个方法内部通过SpringFactoriesLoader.loadFactoryNames()读取所有jar包下的META-INF/spring.factories文件,获取EnableAutoConfiguration对应的配置类列表。这些配置类可能上百个,但不会全部生效,而是通过@Conditional系列注解进行条件判断——比如@ConditionalOnClass要求类路径存在某个类,@ConditionalOnMissingBean要求容器中没有某个Bean。只有满足所有条件的配置类才会被加载。”
第三层(源码级):
“在源码层面,ConfigurationClassPostProcessor会处理所有配置类,当遇到@Import(AutoConfigurationImportSelector.class)时,会调用其selectImports方法。AutoConfigurationImportSelector获取候选配置后,会通过SpringFactoriesLoader加载所有AutoConfigurationImportFilter,这些filter会执行@Conditional的条件判断,最终返回符合条件的配置类。Spring再将这些配置类注册到容器中,执行其中的@Bean方法,完成自动装配。”
第四层(扩展):
“这种设计体现了开闭原则——对扩展开放,对修改关闭。我们想要自定义starter时,只需要按规范编写自动配置类和spring.factories文件,SpringBoot就能自动加载。同时,条件装配提供了极大的灵活性,让自动配置既强大又不侵入用户的自定义配置。”
十、常见面试追问
Q1:@ConditionalOnMissingBean的作用是什么?
“它表示当容器中没有指定Bean时才创建当前Bean。这保证了用户的自定义配置优先级高于自动配置——如果用户自己定义了一个DataSource,自动配置就不会再创建默认的DataSource。”
Q2:spring.factories文件在哪里?可以自定义吗?
“它在每个starter的META-INF目录下。我们可以自定义,比如写一个自己的starter,在里面放上spring.factories,SpringBoot启动时会自动读取。”
Q3:多个自动配置类之间有依赖关系怎么办?
“可以通过@AutoConfigureAfter、@AutoConfigureBefore、@AutoConfigureOrder来指定加载顺序。比如DataSourceTransactionManagerAutoConfiguration通常会在DataSourceAutoConfiguration之后加载。”
Q4:如何查看哪些自动配置生效了?
“在application.yml中设置
debug: true,启动时控制台会打印Positive matches(生效的)和Negative matches(未生效的)配置类,非常直观。”
Q5:自动配置和普通配置的优先级?
“用户自定义配置 > 自动配置。因为自动配置类上通常有@ConditionalOnMissingBean,只要用户自己定义了,自动配置就不会覆盖。”
夜雨聆风