Spring Boot 自动加载机制详解:从原理到手写一个 Starter

Spring Boot 自动加载机制详解:从原理到手写一个 Starter

前言

Spring Boot 最吸引人的地方之一,就是“开箱即用”。

引入 spring-boot-starter-web,Tomcat、Spring MVC、JSON 转换器都准备好了;引入 spring-boot-starter-data-redis,Redis 相关组件就能按配置自动装配;引入数据库驱动和连接池,DataSource 也会自动出现。

这背后不是魔法,而是一套非常清晰的机制:自动配置(Auto-configuration)

本文分两部分:

  1. 先讲 Spring Boot 自动加载机制到底如何工作。
  2. 再手写一个 jar,让业务项目只要引入依赖,就能自动加载我们的 Bean。

目标是读完之后,你不仅知道 @SpringBootApplication 为什么能启动一堆东西,还能自己写一个类似 xxx-spring-boot-starter 的组件。

一、先看一个现象

一个最小 Spring Boot 应用通常只有几行代码:

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

没有写:

@EnableWebMvc
@ComponentScan(...)
@Bean
public DispatcherServlet dispatcherServlet() { ... }

但 Web 应用仍然能启动。

原因是 @SpringBootApplication 不是一个普通注解,它内部组合了几个关键能力:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
}

其中和“自动加载”关系最大的就是:

@EnableAutoConfiguration

它负责打开 Spring Boot 的自动配置入口。

二、什么是自动配置

Spring 官方文档对自动配置的解释可以概括为一句话:

Spring Boot 会根据你添加到 classpath 中的 jar 依赖,尝试自动配置你的 Spring 应用。

比如:

  • classpath 里有 Spring MVC,Spring Boot 尝试配置 Web MVC。
  • classpath 里有 Jackson,Spring Boot 尝试配置 JSON 序列化。
  • classpath 里有 HSQLDB,并且你没有手动配置 DataSource,Spring Boot 可以自动配置内存数据库。
  • classpath 里有 Redis 客户端,并且配置了 Redis 地址,Spring Boot 可以自动配置 Redis 相关 Bean。

重点是两个词:

classpath + 条件判断

不是只要有自动配置类就一定生效。Spring Boot 会先判断条件,条件满足才注册 Bean。

三、自动配置的核心链路

可以把 Spring Boot 自动加载理解成这条链:

@SpringBootApplication
        ↓
@EnableAutoConfiguration
        ↓
加载自动配置候选类
        ↓
逐个判断 @Conditional 条件
        ↓
条件满足则注册 @Bean
        ↓
用户自定义 Bean 优先,自动配置退让

这里有三个核心点。

1. 自动配置类从哪里来

Spring Boot 会扫描依赖 jar 中的自动配置声明文件。

在 Spring Boot 3.x/4.x 推荐方式中,自动配置类放在这个文件里:

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

文件内容是一行一个自动配置类全限定名:

com.example.demo.autoconfigure.HelloAutoConfiguration
com.example.demo.autoconfigure.HelloWebAutoConfiguration

只要你的 jar 在业务项目 classpath 中,Spring Boot 就能发现这个文件,然后把里面列出的类作为自动配置候选类。

补充:Spring Boot 2.x 常见写法是通过 META-INF/spring.factoriesEnableAutoConfiguration key 注册自动配置类。Spring Boot 3 以后更推荐使用 AutoConfiguration.imports

2. 自动配置类怎么决定是否生效

自动配置类通常不是无条件生效,而是配合条件注解使用。

常见条件注解:

注解 作用
@ConditionalOnClass classpath 中存在某个类时生效
@ConditionalOnMissingBean 容器中不存在某个 Bean 时生效
@ConditionalOnBean 容器中存在某个 Bean 时生效
@ConditionalOnProperty 配置项满足条件时生效
@ConditionalOnWebApplication 当前是 Web 应用时生效
@ConditionalOnResource 指定资源存在时生效

例如:

@AutoConfiguration
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {
}

含义是:只有 classpath 中存在 ObjectMapper,这段配置才有意义。

再看一个更常见的例子:

@Bean
@ConditionalOnMissingBean
public HelloService helloService() {
    return new HelloService();
}

含义是:如果用户自己已经定义了 HelloService,自动配置就不再创建默认 Bean。

这就是 Spring Boot 自动配置的一个重要原则:

Spring Boot 提供默认值,但用户显式配置优先。

3. 自动配置不是组件扫描

很多人第一次写 starter 时会犯一个错误:以为把类放进 jar,业务项目就会自动扫描到。

实际上,业务项目的 @ComponentScan 默认只扫描启动类所在包及其子包。你的 starter jar 通常不在这个包下面,所以不会被扫描。

正确做法不是让业务项目扫描你的 jar,而是让你的 jar 提供自动配置声明:

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

Spring Boot 通过这个文件找到你的自动配置类。

四、自动配置和 starter 的关系

很多依赖名字里都有 starter,比如:

spring-boot-starter-web
spring-boot-starter-data-redis
my-company-spring-boot-starter

starter 本质上不是特殊技术,而是一种依赖组织方式。

通常一个 starter 会做两件事:

  1. 引入功能所需的依赖。
  2. 引入自动配置模块,让 Spring Boot 能自动创建相关 Bean。

可以简单理解:

starter = 依赖集合 + 自动配置入口

在企业内部,我们经常会做自己的 starter,例如:

  • 统一日志 starter
  • 统一鉴权 starter
  • 统一异常处理 starter
  • 统一 Redis 工具 starter
  • 统一接口签名 starter
  • 统一消息发送 starter

业务项目只需要引入依赖:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>hello-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

然后配置几项属性,就能直接使用相关能力。

五、手写一个最小 starter

我们写一个非常简单的 starter:

业务项目引入 jar 后,可以自动注入一个 HelloService

使用方式:

@RestController
public class HelloController {

    private final HelloService helloService;

    public HelloController(HelloService helloService) {
        this.helloService = helloService;
    }

    @GetMapping("/hello")
    public String hello() {
        return helloService.sayHello("Spring Boot");
    }
}

配置:

hello:
  enabled: true
  prefix: "你好"

访问 /hello 返回:

你好, Spring Boot

六、项目结构

创建一个模块:

hello-spring-boot-starter
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           └── hello
        │               ├── HelloProperties.java
        │               ├── HelloService.java
        │               └── autoconfigure
        │                   └── HelloAutoConfiguration.java
        └── resources
            └── META-INF
                └── spring
                    └── org.springframework.boot.autoconfigure.AutoConfiguration.imports

注意自动配置类的位置:

com.example.hello.autoconfigure.HelloAutoConfiguration

它最好放在一个明确的 autoconfigure 包里,不要指望业务项目通过组件扫描发现它。

七、编写 pom.xml

示例基于 Spring Boot 3.x:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>hello-spring-boot-starter</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <java.version>17</java.version>
        <spring-boot.version>3.3.0</spring-boot.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

这里重点是:

spring-boot-autoconfigure

它提供了 @AutoConfiguration@ConditionalOnMissingBean@ConditionalOnProperty 等自动配置相关能力。

spring-boot-configuration-processor 用来生成配置元数据,让使用方在 IDE 中写 hello.prefix 时有提示。

八、编写配置属性类

package com.example.hello;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "hello")
public class HelloProperties {

    /**
     * 是否启用 HelloService 自动配置。
     */
    private boolean enabled = true;

    /**
     * 问候语前缀。
     */
    private String prefix = "Hello";

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }
}

这个类负责把配置文件中的:

hello:
  enabled: true
  prefix: "你好"

绑定成 Java 对象。

九、编写业务能力类

package com.example.hello;

public class HelloService {

    private final HelloProperties properties;

    public HelloService(HelloProperties properties) {
        this.properties = properties;
    }

    public String sayHello(String name) {
        return properties.getPrefix() + ", " + name;
    }
}

这里不要加 @Service

原因是我们不希望它依赖组件扫描,而是由自动配置类决定什么时候创建。

十、编写自动配置类

package com.example.hello.autoconfigure;

import com.example.hello.HelloProperties;
import com.example.hello.HelloService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

@AutoConfiguration
@EnableConfigurationProperties(HelloProperties.class)
@ConditionalOnProperty(
        prefix = "hello",
        name = "enabled",
        havingValue = "true",
        matchIfMissing = true
)
public class HelloAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public HelloService helloService(HelloProperties properties) {
        return new HelloService(properties);
    }
}

逐行解释:

@AutoConfiguration

声明这是一个 Spring Boot 自动配置类。

@EnableConfigurationProperties(HelloProperties.class)

启用配置属性绑定,让 hello.prefix 这类配置能绑定到 HelloProperties

@ConditionalOnProperty(...)

控制配置开关。默认启用,用户可以通过下面配置关闭:

hello:
  enabled: false
@ConditionalOnMissingBean

如果业务项目自己定义了 HelloService,自动配置就退让,不再创建默认对象。

这正是 Spring Boot starter 应该遵循的设计:提供默认实现,但允许用户覆盖。

十一、注册自动配置类

创建文件:

src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

内容:

com.example.hello.autoconfigure.HelloAutoConfiguration

注意:

  • 一行一个自动配置类。
  • 可以用 # 写注释。
  • 自动配置类应该通过这个文件加载,不应该依赖业务项目组件扫描。
  • 如果有多个自动配置类,需要考虑顺序,可以用 @AutoConfigureBefore@AutoConfigureAfter@AutoConfiguration(before = ..., after = ...)

到这里,一个最小 starter 就完成了。

十二、在业务项目中使用

业务项目引入依赖:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>hello-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

配置:

hello:
  prefix: "你好"

使用:

package com.example.demo;

import com.example.hello.HelloService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    private final HelloService helloService;

    public HelloController(HelloService helloService) {
        this.helloService = helloService;
    }

    @GetMapping("/hello")
    public String hello() {
        return helloService.sayHello("Spring Boot");
    }
}

启动业务项目后,HelloService 会自动注入。

业务项目没有写:

@Bean
public HelloService helloService() { ... }

也没有扫描 starter 包。

这就是自动配置的效果。

十三、用户如何覆盖默认 Bean

如果业务项目不想使用 starter 默认实现,可以自己声明一个 Bean:

@Configuration
public class CustomHelloConfiguration {

    @Bean
    public HelloService helloService() {
        HelloProperties properties = new HelloProperties();
        properties.setPrefix("自定义");
        return new HelloService(properties);
    }
}

由于自动配置类中有:

@ConditionalOnMissingBean

Spring Boot 会检测到容器里已经有 HelloService,于是不会再创建 starter 中的默认 Bean。

这就是“自动配置退让机制”。

十四、如何验证自动配置是否生效

1. 启动时开启 debug

启动应用时加:

java -jar app.jar --debug

或者配置:

debug=true

Spring Boot 会输出条件评估报告,告诉你哪些自动配置生效了,哪些没有生效,以及原因是什么。

2. 使用 Actuator conditions

如果项目引入了 Actuator,可以查看 conditions 端点:

/actuator/conditions

它能帮助排查自动配置为什么没有生效。

3. 写自动配置测试

Spring Boot 官方推荐使用 ApplicationContextRunner 测试自动配置。

示例:

package com.example.hello.autoconfigure;

import com.example.hello.HelloService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

import static org.assertj.core.api.Assertions.assertThat;

class HelloAutoConfigurationTest {

    private final ApplicationContextRunner contextRunner =
            new ApplicationContextRunner()
                    .withConfiguration(AutoConfigurations.of(HelloAutoConfiguration.class));

    @Test
    void shouldCreateHelloServiceByDefault() {
        contextRunner.run(context -> {
            assertThat(context).hasSingleBean(HelloService.class);
            assertThat(context.getBean(HelloService.class).sayHello("Boot"))
                    .isEqualTo("Hello, Boot");
        });
    }

    @Test
    void shouldUseConfiguredPrefix() {
        contextRunner
                .withPropertyValues("hello.prefix=你好")
                .run(context -> {
                    assertThat(context.getBean(HelloService.class).sayHello("Boot"))
                            .isEqualTo("你好, Boot");
                });
    }

    @Test
    void shouldNotCreateHelloServiceWhenDisabled() {
        contextRunner
                .withPropertyValues("hello.enabled=false")
                .run(context -> {
                    assertThat(context).doesNotHaveBean(HelloService.class);
                });
    }
}

测试 starter 时,不建议直接启动一个完整 Spring Boot 应用。ApplicationContextRunner 更轻量,也更适合组合不同条件。

十五、常见问题

1. 为什么我的自动配置类没有生效

优先检查四件事:

  1. jar 是否真的在业务项目 classpath 中。
  2. AutoConfiguration.imports 路径是否正确。
  3. 文件中写的自动配置类全限定名是否正确。
  4. 条件注解是否没有满足,比如配置项关闭、缺少依赖、用户已经定义了同类型 Bean。

2. 为什么不要在自动配置类上使用 @ComponentScan

自动配置应该是精确、可控的。

如果你在自动配置类上加 @ComponentScan,可能会把一堆不该注册的类扫描进业务项目,导致 Bean 冲突、启动变慢、行为不可预测。

Spring 官方文档也明确建议:自动配置类不要通过组件扫描去找额外组件,需要引入其他配置时应使用明确的 @Import

3. @Configuration@AutoConfiguration 有什么区别

@AutoConfiguration 本质上也是配置类,但它表达的是“这是给 Spring Boot 自动配置机制使用的配置类”。

普通业务配置类通常由组件扫描发现。
自动配置类应该由 AutoConfiguration.imports 声明后被 Spring Boot 加载。

4. starter 一定要叫 spring-boot-starter

不一定。

命名只是约定。官方 starter 通常叫:

spring-boot-starter-xxx

第三方或公司内部 starter 通常建议叫:

xxx-spring-boot-starter

例如:

mybatis-spring-boot-starter
company-log-spring-boot-starter

这样可以避免和官方 starter 混淆。

5. 一个 starter 里能有多个自动配置类吗

可以。

例如:

com.example.hello.autoconfigure.HelloAutoConfiguration
com.example.hello.autoconfigure.HelloWebAutoConfiguration
com.example.hello.autoconfigure.HelloMetricsAutoConfiguration

分别处理基础能力、Web 能力、监控能力。

如果它们之间有顺序要求,再使用 @AutoConfigureAfter@AutoConfigureBefore

十六、设计一个好 starter 的原则

1. 默认可用,但允许覆盖

starter 应该提供合理默认值,但不能绑死用户。

典型做法:

@ConditionalOnMissingBean

让业务项目可以通过自定义 Bean 覆盖默认实现。

2. 功能要有开关

建议提供类似配置:

hello:
  enabled: true

并配合:

@ConditionalOnProperty(prefix = "hello", name = "enabled", matchIfMissing = true)

用户遇到问题时可以快速关闭。

3. 条件要尽量精确

不要无条件创建一堆 Bean。

如果依赖某个类:

@ConditionalOnClass(SomeClient.class)

如果只在 Web 环境生效:

@ConditionalOnWebApplication

如果依赖配置项:

@ConditionalOnProperty(...)

条件越精确,starter 对业务项目的侵入越小。

4. 不要抢业务项目的控制权

starter 是提供默认能力,不是接管项目。

避免:

  • 随意扫描业务包
  • 随意覆盖用户 Bean
  • 在启动时做耗时网络请求
  • 在自动配置阶段执行复杂业务逻辑
  • 默认开启高风险行为

5. 提供配置元数据和文档

如果 starter 暴露配置项,建议引入:

spring-boot-configuration-processor

并为配置字段写注释。这样使用方在 IDE 中能看到提示。

同时要写清楚:

  • 需要引入什么依赖
  • 支持哪些配置项
  • 默认行为是什么
  • 如何关闭
  • 如何覆盖默认 Bean

十七、完整加载流程回顾

业务项目引入 starter 后,大致流程如下:

1. 业务项目启动
2. @SpringBootApplication 启用自动配置
3. Spring Boot 读取 classpath 中的 AutoConfiguration.imports
4. 找到 HelloAutoConfiguration
5. 判断 @ConditionalOnProperty 等条件
6. 绑定 HelloProperties
7. 如果容器中没有 HelloService,则创建默认 HelloService
8. 业务代码通过构造方法注入 HelloService

这就是“写一个 jar,让 Spring Boot 自动加载起来”的核心。

总结

Spring Boot 自动配置的本质不是神秘的黑盒,而是一套清晰的约定:

  • @SpringBootApplication 开启自动配置。
  • Spring Boot 根据 classpath 和自动配置声明文件寻找候选配置类。
  • 自动配置类通过条件注解决定是否生效。
  • 默认 Bean 通过 @ConditionalOnMissingBean 给用户让路。
  • 自定义 starter 通过 AutoConfiguration.imports 暴露自动配置类。
  • 好的 starter 应该默认可用、条件精确、允许覆盖、可以关闭、易于测试。

如果只记一句话:

starter 负责把 jar 放进 classpath,AutoConfiguration.imports 负责让 Spring Boot 找到配置类,Conditional 注解负责决定是否真正注册 Bean。

掌握这条链路后,再看各种官方 starter 或第三方 starter,就不再是“为什么一引入依赖就能用”的疑问,而是可以清楚地知道:它一定是在某个自动配置类里,根据某些条件,注册了一批默认 Bean。

参考资料

posted @ 2026-06-22 09:52  松鼠航  阅读(16)  评论(0)    收藏  举报