SpringBoot 自动配置底层原理:@EnableAutoConfiguration 与 @Conditional 条件注解深度解析

一、前言

SpringBoot核心精髓就是约定优于配置 ,不用手动XML、不用大量@Bean手动注册组件;核心驱动是 @EnableAutoConfiguration,再配合一大批@Conditional派生条件注解,实现「类存在、Bean不存在、配置项开启时,才自动向IOC容器注入对应组件」。

前置知识点:

  1. Spring3+注解驱动IOC、@Configuration@Bean@ComponentScan
  2. SPI机制(Java原生服务发现);
  3. Spring4条件注解体系。

二、启动类复合注解拆解:@SpringBootApplication

java 复制代码
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

点开注解源码:

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
    // ...
}

三大核心合成:

  1. @Configuration:当前启动类是配置类;
  2. @ComponentScan:默认扫描启动类所在包及其子包下所有@Component@Service@Controller等组件;
  3. @EnableAutoConfiguration:自动配置开关,整个自动配置的入口

三、@EnableAutoConfiguration 底层执行流程

1. 注解源码

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    // ...
}

关键:@Import(AutoConfigurationImportSelector.class)

AutoConfigurationImportSelector 实现了DeferredImportSelector接口,Spring容器刷新阶段会执行其选择导入配置类的逻辑。

2. 完整加载步骤

  1. AutoConfigurationImportSelector 借助SpringBoot内置SPI,读取所有依赖包中
    META-INF/spring.factories 文件;
  2. 该文件中key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration,value是一大批自动配置类全限定名 (如WebMvcAutoConfigurationDataSourceAutoConfigurationRedisAutoConfiguration);
  3. 批量拿到这些自动配置类,加载进Spring上下文;
  4. 每个自动配置类内部都标注了大量**@Conditional派生条件注解**;
  5. 条件全部满足:该配置类里的@Bean才会实例化,注入IOC容器;条件不满足,直接跳过。

3. spring.factories

properties 复制代码
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

引入对应starter依赖,该配置类才会被扫描到,无依赖则SPI不会加载。

四、核心:@Conditional 派生条件注解体系

1. 顶层父注解:@Conditional

java 复制代码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Conditional {
    Class<? extends Condition>[] value();
}

作用:自定义条件规则,实现Condition接口matches()方法,返回true则生效,false跳过。

SpringBoot在此基础上封装了大量开箱即用的派生注解,无需手写Condition。

2. SpringBoot高频内置派生条件注解(分类)

(1)类存在判定:依赖是否引入
  1. @ConditionalOnClass:classpath中存在指定类,才生效;
  2. @ConditionalOnMissingClass:classpath不存在指定类,才生效。

示例(WebMvc自动配置):

java 复制代码
@Configuration
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
public class WebMvcAutoConfiguration {
    // ...自动注册视图解析器、静态资源处理器、转换器等Bean
}

只要引入spring-boot-starter-web,Servlet、DispatcherServlet类存在,WebMvc自动配置才启用。

(2)容器Bean判定:容器内是否已有该组件
  1. @ConditionalOnBean:IOC容器中已经存在指定Bean,才创建;
  2. @ConditionalOnMissingBean容器中不存在该Bean,才自动注册(最常用!)。

典型场景:用户自定义了MessageConverter,SpringBoot就不再自动注入默认的转换器,实现自定义覆盖默认配置。

java 复制代码
@Bean
@ConditionalOnMissingBean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
    return new MappingJackson2HttpMessageConverter();
}
(3)配置文件属性判定:application.yml是否配置对应key
  1. @ConditionalOnProperty:根据配置文件属性值匹配,开启/关闭组件;
java 复制代码
// 配置项 my.datasource.enabled=true 才生效
@ConditionalOnProperty(prefix = "my.datasource", name = "enabled", havingValue = "true")
(4)Web环境区分
  1. @ConditionalOnWebApplication:仅Web环境生效;
  2. @ConditionalOnNotWebApplication:非Web(Jar命令行、定时任务)环境生效。
(5)资源、系统属性等其他

@ConditionalOnResource@ConditionalOnJava@ConditionalOnJndi等,日常使用频率低。

五、拿WebMvcAutoConfiguration完整举例串联整条链路

  1. 项目引入spring-boot-starter-web,依赖中存在spring.factories,携带WebMvcAutoConfiguration
  2. @EnableAutoConfiguration扫描到该配置类,加载进上下文;
  3. 类上@ConditionalOnClass(Servlet.class)校验通过;
  4. 内部@Bean方法标注@ConditionalOnMissingBean
    • 开发者没手动注册DispatcherServlet、视图解析器 → SpringBoot自动创建并注入;
    • 开发者自己@Bean重写了组件 → 条件不满足,自动配置跳过,以自定义Bean为准;
  5. 最终SpringMVC全套组件自动装配完成,无需XML。

六、application.yml配置绑定原理(@ConfigurationProperties)

自动配置类不会硬编码写死端口、连接地址,而是绑定配置文件属性:

  1. 自动配置类内绑定属性实体类标注@ConfigurationProperties(prefix = "server")
  2. SpringBoot自动把server.portserver.servlet.context-path等yml配置注入属性对象;
  3. 再把属性对象传入自动配置的Bean(如Tomcat容器、DispatcherServlet)。

示例:

java 复制代码
@ConfigurationProperties(prefix = "server")
public class ServerProperties {
    private Integer port = 8080;
   
}

这就是修改yml就能修改内嵌Tomcat端口的底层原因。

七、排除指定自动配置类(禁用自动装配)

方式1:启动类注解排除

java 复制代码
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class DemoApplication {}

场景:纯Web静态项目,无数据库,排除数据源自动配置,避免报错。

方式2:yml配置排除

yaml 复制代码
spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

八、完整执行时序总结

  1. 启动类@SpringBootApplication包含@EnableAutoConfiguration
  2. AutoConfigurationImportSelector通过SPI读取所有starter内spring.factories,拿到候选自动配置类;
  3. 逐个加载配置类,校验类上@ConditionalOnClass等外层条件;
  4. 条件通过后,执行内部@Bean方法,再校验方法上@ConditionalOnMissingBean
  5. 校验全部通过 → Bean实例化,放入IOC容器;任意条件不满足 → 跳过该组件装配;
  6. 配置文件属性通过@ConfigurationProperties绑定,修改组件运行参数。

九、高频面试题

1)@EnableAutoConfiguration做了什么?

借助AutoConfigurationImportSelector + Java SPI,扫描所有依赖包META-INF/spring.factories中定义的自动配置类,批量导入到Spring容器,开启自动装配总开关。

2)@ConditionalOnClass和@ConditionalOnMissingBean区别?

  • @ConditionalOnClass:检测classpath下是否存在对应类,用来判断是否引入了对应starter依赖;
  • @ConditionalOnMissingBean:检测IOC容器有没有用户自定义的Bean,用户自定义优先,框架默认Bean兜底。

3)为什么引入starter不用手动配置组件?

starter内部依赖包含自动配置类,写入spring.factories;EnableAutoConfiguration加载配置类;条件注解校验依赖、Bean、配置项后自动创建组件,实现零XML配置。