java 技术随笔

Spring Boot 自动配置原理与自定义 Starter 实战

“为什么引入一个 starter 依赖,什么都不配置就能用?”、“Redis、DataSource 这些 Bean 到底是谁创建的?”——答案都指向 Spring Boot 的自动配置(Auto Configuration)机制。本文从启动注解拆起,讲清自动配置的加载链路与条件装配,并手把手实现一个自定义 Starter。

一、@SpringBootApplication 拆解

这个“三合一”组合注解由三个核心注解组成:

@SpringBootConfiguration   // 等价于 @Configuration:标记为配置类
@EnableAutoConfiguration   // 自动配置的总开关(核心)
@ComponentScan             // 扫描主类所在包及其子包的 @Component/@Service/@Repository/@Controller
public class Application { ... }

注意 @ComponentScan 默认只扫主类所在包及其子包——这也是“为什么 controller 放错包就不被识别”的原因。

二、自动配置的原理:AutoConfigurationImportSelector

自动配置的入口是 @EnableAutoConfiguration,它通过 @Import(AutoConfigurationImportSelector.class) 引入了选择器。Spring 容器启动时,选择器会调用 getCandidateConfigurations()

// 简化逻辑:从 classpath 读取自动配置类的候选列表
protected List<String> getCandidateConfigurations(...) {
    // Spring Boot 2.7 之后从 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 读取
    // Spring Boot 2.6 及以前读取 META-INF/spring.factories 中 key=org.springframework.boot.autoconfigure.EnableAutoConfiguration
    List<String> configurations = ...;
    return configurations;
}

加载链路一句话:@EnableAutoConfiguration → AutoConfigurationImportSelector → 读取 classpath 下所有 jar 里的自动配置类列表 → 逐个按 @Conditional 条件判断 → 满足条件的配置类生效,注册相关 Bean

这意味着每个 starter jar 里都“藏”着自动配置类,但是否真正创建 Bean,取决于条件注解是否命中——例如没有引入 Redis 相关类,RedisAutoConfiguration 的条件就不满足,不会注册任何多余 Bean。

三、条件装配:@Conditional 家族

条件注解决定了配置类“装还是不装”,开发中高频使用的有:

注解生效条件典型用途
@ConditionalOnClass / OnMissingClassclasspath 存在 / 不存在某类按依赖判断(如引入 redis 客户端才配置)
@ConditionalOnBean / OnMissingBean容器存在 / 不存在某 Bean用户自定义了同名 Bean 就“让位”(尊重覆盖)
@ConditionalOnProperty配置项存在且值匹配开关控制,如 xxx.enabled=true 才生效
@ConditionalOnWebApplication当前是 Web 环境区分 MVC / WebFlux
@ConditionalOnMissingBean(name=...)缺失指定名称的 Bean提供默认实现、允许用户覆盖
// 典型的自动配置类骨架(以自定义 demo 为例)
@AutoConfiguration                    // Spring Boot 2.7+ 推荐的注册注解
@ConditionalOnClass(HelloService.class)
@ConditionalOnProperty(prefix = "demo", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(DemoProperties.class)
public class DemoAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public HelloService helloService(DemoProperties props) {
        return new HelloService(props.getPrefix());
    }
}

四、手写一个自定义 Starter

Starter 通常包含两个模块:自动配置模块(autoconfigure,含配置类)和 Starter 模块(仅声明对 autoconfigure 的依赖)。实际中常合并为单个模块,下面给出最小可用实现。

第 1 步:创建模块并编写配置属性类

// DemoProperties.java —— 把 application.yml 里的配置映射成类
@ConfigurationProperties(prefix = "demo")
public class DemoProperties {
    private String prefix = "Hello";   // 默认值
    private String suffix = "!";
    // getter / setter 略
}

第 2 步:编写业务类与自动配置类

// HelloService.java —— 对外提供的“服务”
public class HelloService {
    private final DemoProperties props;
    public HelloService(DemoProperties props) { this.props = props; }
    public String hello(String name) {
        return props.getPrefix() + ", " + name + props.getSuffix();
    }
}

// DemoAutoConfiguration.java
@AutoConfiguration
@ConditionalOnClass(HelloService.class)
@EnableConfigurationProperties(DemoProperties.class)
@ConditionalOnProperty(prefix = "demo", name = "enabled", matchIfMissing = true)
public class DemoAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(HelloService.class)
    public HelloService helloService(DemoProperties props) {
        return new HelloService(props);
    }
}

第 3 步:注册自动配置类(关键一步,容易被漏)

# 在 resources/META-INF/ 下新建(Spring Boot 2.7+)
# 文件:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.demo.autoconfigure.DemoAutoConfiguration

# —— 若使用 Spring Boot 2.6 及以前,则写进 spring.factories ——
# org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
# com.example.demo.autoconfigure.DemoAutoConfiguration

第 4 步:业务项目引入并测试

// 引入依赖 demo-spring-boot-starter 后,直接注入即可使用
@RestController
public class HelloController {
    @Autowired
    private HelloService helloService;

    @GetMapping("/hello")
    public String hello(@RequestParam String name) {
        return helloService.hello(name);   // 输出: Hi, Bob!
    }
}
# application.yml 中可覆盖默认前缀
demo:
  prefix: Hi
  # enabled: false   # 关闭此配置类

五、开发中常见问题

1. 想看哪些自动配置生效了? 在 application.yml 加 debug: true,启动日志会输出 “Positive matches”(生效的)和 “Negative matches”(未生效及原因),排查“为什么没生效”非常有用。

2. 想排除某个自动配置? 两种方式:@SpringBootApplication(exclude = DataSourceAutoConfiguration.class),或在配置文件里 spring.autoconfigure.exclude=...

3. 为什么我自定义的 Bean 被自动配置的覆盖了? 自动配置类大多用 @ConditionalOnMissingBean 兜底,你自己 @Bean/@Component 注册的同名 Bean 会优先生效——这正是它“可被用户覆盖”的设计。

4. 多个自动配置有依赖顺序怎么办?@AutoConfigureBefore / @AutoConfigureAfter / @AutoConfigureOrder 控制先后(G1 场景如先配连接池再配 MyBatis)。

六、小结

自动配置的底层三件套是:配置文件列表(imports/spring.factories)+ 条件注解(@Conditional 系列)+ 配置属性绑定(@ConfigurationProperties)。看懂这一条链路,不仅能解释“为什么 starter 即插即用”,写中间件、做组件封装时也能自己“造一个 starter”。理解它,你就真正跨过了 Spring Boot“会用”到“懂原理”的门槛。

标签
Spring Boot自动配置Starter原理