乐于分享
好东西不私藏

SpringBoot自动装配原理:从源码到实战,彻底搞懂

SpringBoot自动装配原理:从源码到实战,彻底搞懂

一、什么是自动装配?

先看一个最直观的例子:
@SpringBootApplication  // 就这一个注解public class MyApplication {    publicstaticvoidmain(String[] args) {        SpringApplication.run(MyApplication.classargs);    }}

就这么简单,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 {    // ...}

两个关键点:

  1. @AutoConfigurationPackage:自动配置包,稍后说

  2. @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<StringgetCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(            getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());        return configurations;    }}

核心逻辑

  1. 从所有META-INF/spring.factories中读取EnableAutoConfiguration对应的值

  2. 得到上百个候选配置类

  3. 根据@Conditional条件进行过滤

  4. 返回最终生效的配置类

五、条件装配:@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)  // 用户没有自己定义DataSource    static class PooledDataSourceConfiguration {        @Bean        @ConditionalOnMissingBean  // 没有自定义时才创建        public DataSource dataSource() {            // 创建数据源(HikariCP、Tomcat JDBC等)            return createDataSource();        }    }}

判断逻辑

  1. 先看DataSource.class是否存在(有数据库驱动才会存在)

  2. 再看用户有没有自己定义DataSource Bean

  3. 都没有,才创建默认的DataSource

六、完整流程源码追踪

6.1 从@SpringBootApplication开始

// 启动类@SpringBootApplicationpublic class App {    publicstaticvoidmain(String[] args) {        // 1. 创建SpringApplication        SpringApplication.run(App.classargs);    }}

6.2 进入refresh方法

// AbstractApplicationContext.refresh()publicvoidrefresh() throws BeansException {    // ...    // 调用所有BeanFactoryPostProcessor    invokeBeanFactoryPostProcessors(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:执行selectImports                ImportSelector selector = (ImportSelectorloadClass(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<StringgetCandidateConfigurations(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<Stringfilter(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.classargs);        // 直接从容器获取HelloService        HelloService 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,只要用户自己定义了,自动配置就不会覆盖。”

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » SpringBoot自动装配原理:从源码到实战,彻底搞懂

猜你喜欢

  • 暂无文章