SPRING BOOT · AUTO-CONFIGURATION · JDK 8 / 17

加一行依赖,为什么就能直接用?

Spring Boot 是一支拎包入住的装修队。你选礼包,它照着一份清单,由检查员挨个确认后,把该装的 Bean 装好。

礼包Starter

pom.xml 里加一行,一整套零件就送到 classpath 上。

spring-boot-starter-xxx
清单AutoConfiguration

一百多张安装卡片。每张写着:满足什么条件,就装哪些 Bean。

登记在 AutoConfiguration.imports
检查员@Conditional

装之前挨个问:零件到了吗?你自己装过没?开关开了吗?

@ConditionalOnXxx 一家子
配角 总开关 @SpringBootApplication 要求单 application.yml,写你的偏好 验收单 debug=true 打出的条件报告

一张图看原理

总开关拆开是三个注解。真正干活的是第三个,它带来一个“选择器”,负责下面整个漏斗。

@SpringBootApplication三合一
@SpringBootConfiguration它自己就是个配置类
@ComponentScan扫描你自己的包,先登记你的 Bean
@EnableAutoConfiguration打开自动配置
@Import(AutoConfigurationImportSelector)
选择器:读清单、筛选、排序
读清单:一百多个候选Boot 3:META-INF/spring/…AutoConfiguration.imports Boot 2:spring.factories
划掉你 exclude 的@SpringBootApplication(exclude = …) 或 spring.autoconfigure.exclude
快速初筛:零件不在的先划掉不加载类,查一张预先算好的表 spring-autoconfigure-metadata.properties
排好先后@AutoConfiguration(after = DataSourceAutoConfiguration.class)
检查员逐张细查 @Conditional通过的卡片,登记它的 Bean 图纸
留下几十张,真正生效

装修现场

左边勾选你做了什么,右边看 8 张常见的清单卡片谁装上、谁跳过、谁让位给你。默认是一个典型的 Web + 数据库项目。

pom.xml 里的礼包

application.yml

你自己的代码

✓
启动成功
装上让位给你跳过启动失败

自动配置在启动的哪一步

SpringApplication.run() 的八站。你的 Bean 在第 4 站登记,自动配置在第 5 站才登场。

  1. 1
    判断应用类型看 classpath:Servlet、Reactive 还是非 Web
  2. 2
    准备环境读 application.yml、环境变量、命令行参数
  3. 3
    创建容器ApplicationContext
  4. 4
    登记你的 Bean@ComponentScan 扫到的类、你的 @Configuration
  5. 5
    自动配置登场读清单、筛选、登记 Bean 图纸
  6. 6
    建内嵌 TomcatonRefresh()
  7. 7
    按图纸造 BeanDataSource、DispatcherServlet……
  8. 8
    开门营业监听端口,调用 ApplicationRunner

为什么你的 Bean 说了算

第 4 站先登记你的;第 5 站检查员才问“你自己装过没”。@ConditionalOnMissingBean 看到你的,就让位。

自动配置只画图纸

第 5 站只登记 BeanDefinition,真正 new 出来在第 7 站。所以条件判断发生在任何 Bean 被创建之前。

检查员的八个问题

清单卡片上最常见的条件注解。加粗的那个最重要:它让你能覆盖任何默认配置。

@ConditionalOnClass

零件到了吗?classpath 里有这个类

@ConditionalOnMissingClass

classpath 里没有这个类

@ConditionalOnBean

容器里已经有这种 Bean

@ConditionalOnMissingBean

你自己没装过。装过就让位

@ConditionalOnProperty

配置开关打开了吗

@ConditionalOnWebApplication

这是 Web 应用吗

@ConditionalOnSingleCandidate

这种 Bean 只有一个,或者有一个 @Primary

@ConditionalOnResource

某个资源文件存在吗

想改默认配置,从轻到重四种做法

能用轻的就别用重的。越往右,你要自己负责的东西越多。

改要求单

application.yml 里写 server.port: 9090、spring.datasource.url。最常用。

加料不替换

写一个 Customizer,比如 WebMvcConfigurer、Jackson2ObjectMapperBuilderCustomizer,在默认基础上改。

自己装一个

写自己的 @Bean。带 @ConditionalOnMissingBean 的默认 Bean 会让位。

整张卡片划掉

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

验收单:在 application.yml 写 debug: true,启动日志里会多出这份报告(节选)。Actuator 的 /actuator/conditions 也能看。
============================
CONDITIONS EVALUATION REPORT
============================

Positive matches:          ← 装上的
-----------------
   DataSourceAutoConfiguration matched:
      - @ConditionalOnClass found required classes 'javax.sql.DataSource',
        'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition)

Negative matches:          ← 跳过的,并写明哪个条件没过
-----------------
   RedisAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class
           'org.springframework.data.redis.core.RedisOperations' (OnClassCondition)

Exclusions:                 ← 被你 exclude 的
-----------
    None

自己做一个礼包

公司内部的通用组件,做成 starter,别的项目加一行依赖就能用。命名习惯:官方叫 spring-boot-starter-xxx,第三方叫 xxx-spring-boot-starter(比如 mybatis-spring-boot-starter)。

hello-spring-boot-starter礼包:只有一个 pom,没有代码
依赖 ↓
hello-spring-boot-autoconfigure清单卡片 + 登记文件
hello-core真正的功能代码
版本:
@AutoConfiguration                                     // 一张清单卡片
@ConditionalOnClass(HelloService.class)                // 零件在才装
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean                          // 用户自己装了就让位
    @ConditionalOnProperty(prefix = "hello", name = "enabled", matchIfMissing = true)
    public HelloService helloService(HelloProperties props) {
        return new HelloService(props.getGreeting());
    }
}

@ConfigurationProperties(prefix = "hello")              // 读 application.yml 里的 hello.*
public class HelloProperties {
    private String greeting = "你好";
    // getter / setter
}
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importscom.acme.hello.HelloAutoConfiguration

JDK 8 和 JDK 17:清单换了个地方放

JDK 8 对应 Boot 2,JDK 17 对应 Boot 3。礼包、清单、检查员三个角色都还在。

Spring Boot 2.x

JDK 8+
  • 清单写在 META-INF/spring.factories,键是 EnableAutoConfiguration
  • 卡片就是普通 @Configuration,先后靠 @AutoConfigureAfter
  • 2.7 起新增 .imports 文件和 @AutoConfiguration,两种写法都认
  • 条件里的 Web 类名是 javax.servlet.*

Spring Boot 3.x

JDK 17+
  • 只认 META-INF/spring/…AutoConfiguration.imports;登记在 spring.factories 里的自动配置不再加载
  • 一个 @AutoConfiguration(after = …) 同时管“我是卡片”和先后顺序
  • Web 类名换成 jakarta.servlet.*。还写 javax 的老卡片,@ConditionalOnClass 找不到类,静默跳过
  • 打 GraalVM 原生镜像时,条件在构建期就判定好,运行时不再变

javax.sql.DataSource 在 Boot 3 里也没改名:它属于 JDK 本身,不归 Jakarta EE 管。

没变的:@SpringBootApplication 三合一条件注解你的 Bean 优先debug=true 验收单

常踩的坑

大多数“为什么没生效”,打开验收单就能找到原因。

升级 Boot 3 后,自己写的 starter 不生效

清单还登记在 spring.factories。挪到 AutoConfiguration.imports 文件。

在自己的配置类上用 @ConditionalOnBean

它只看“此刻已经登记”的 Bean,结果取决于顺序。官方建议只在自动配置类里用。

自动配置类被 @ComponentScan 扫到

它被当成你的 Bean 提前登记,失去“最后再看你装没装”的位置。放在扫描不到的包里,只靠清单加载。

加了 jdbc 礼包,没配数据库

启动报 Failed to configure a DataSource。配上 spring.datasource.url,或 exclude DataSourceAutoConfiguration。

加了 security 礼包,接口全变 401

默认的 SecurityFilterChain 生效了:所有接口都要登录。写一个自己的 SecurityFilterChain。

自己定义了 RedisTemplate,结果多出一个

这张卡片按名字判断:@ConditionalOnMissingBean(name = "redisTemplate")。你的 Bean 换了名字,它照样再建一个。

加了 @EnableWebMvc,静态资源和 Jackson 配置失效

WebMvcAutoConfiguration 整张卡片让位。只想加配置,实现 WebMvcConfigurer 就够了。

不知道某个 Bean 为什么没装上

写 debug: true 看验收单,或访问 /actuator/conditions。