Spring Boot Starter 原理

候选人小周在面试美团P6时,简历上写着"熟练使用 Spring Boot,有 spring-boot-starter-web 开发经验"。

面试官问:"你用过 spring-boot-starter-web,那你知道它里面包含什么代码吗?"

小周说:"包含 Web 服务器、Spring MVC 相关的依赖吧?"面试官追问:"那 starter 本身有多少代码?"小周答不上来。面试官又问:"spring-boot-starter-web 和 spring-boot-starter 是什么关系?"

小周支支吾吾,最后说:"后者是基础包?"

【面试官心理】

这道题我用来测试候选人对 Spring Boot "依赖传递"和"配置解耦"的理解程度。知道 starter 是一个纯依赖集合的占 60%,能说出自动配置类加载机制的占 30%,能讲清楚 Spring Boot 3.x 变化的只有 10%。这道题真正区分了"会用"和"会设计"。

一、Starter 到底是什么 🔴

1.1 Starter 的本质:一组依赖坐标

Spring Boot 的 Starter 本身不包含任何 Java 代码。它的本质就是一个 Maven pom.xml,里面定义了一堆依赖坐标。

<!-- spring-boot-starter-web/pom.xml -->
<project>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <dependencies>
        <!-- 引入 spring-boot-starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- 引入 spring-boot-starter-tomcat -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        <!-- 引入 spring-web -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
        <!-- 引入 spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>
    </dependencies>
</project>

所有真正的代码逻辑都在被依赖的那些 jar 包里。spring-boot-starter-web 只是把它们组装起来。

1.2 Starter 的命名规范

Spring Boot 官方对 Starter 的命名有严格规范:

类型命名格式示例
官方 Starterspring-boot-starter-*spring-boot-starter-web
官方非自动配置spring-boot-starter-* (无自动配置)spring-boot-starter-logging
第三方 Starter*-spring-boot-startermybatis-spring-boot-starter
自定义 Starterxxx-spring-boot-starterourcompany-ourmodule-spring-boot-starter
⚠️

命名规范不是强制的,但遵循规范能让使用者一眼就知道这个 starter 是官方的还是第三方贡献的。*-spring-boot-starter 的命名在 Maven central 上有更好的搜索可见性。

1.3 依赖传递链路

spring-boot-starter-web
├── spring-boot-starter          # 核心 Starter(自动配置 + 日志 + Spring Boot 核心)
│   ├── spring-boot               # Spring Boot 核心类
│   └── spring-boot-autoconfigure # 自动配置逻辑(关键!)
│       └── META-INF/
│           └── spring.factories  # 或 AutoConfiguration.imports
├── spring-boot-starter-tomcat   # 嵌入式 Tomcat
│   └── tomcat-embed-core
├── spring-web                    # Spring Web 核心
└── spring-webmvc                 # Spring MVC

注意看:spring-boot-autoconfigure 才是真正包含自动配置逻辑的地方。spring-boot-starter-web 通过传递依赖把它引入进来。

二、Starter 的自动配置机制 🟡

2.1 自动配置类在哪里

spring-boot-autoconfigure jar 包中包含了所有自动配置类:

// spring-boot-autoconfigure-xxx.jar
org.springframework.boot.autoconfigure/
├── jdbc/
│   └── DataSourceAutoConfiguration.class
├── web/
│   └── servlet/
│       ├── WebMvcAutoConfiguration.class
│       └── EmbeddedTomcat.class
├── data/
│   └── redis/
│       └── RedisAutoConfiguration.class
└── ...

每个自动配置类都标注了 @Conditional 系列条件注解,只有满足条件才会注册到容器中。

2.2 配置类的加载

自动配置类通过 spring.factories(Spring Boot 2.x)或 AutoConfiguration.imports(Spring Boot 3.x)被 Spring Boot 发现。

spring-boot-autoconfigure.jar
└── META-INF/
    ├── spring.factories                              # Spring Boot 2.x
    │   └── org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    │       org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
    │       ...
    └── spring/
        └── org.springframework.boot.autoconfigure.AutoConfiguration.imports  # Spring Boot 3.x
            org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
            ...

三、Fatjar 结构 🟡

3.1 spring-boot-maven-plugin 的作用

当你在 pom.xml 中配置了 spring-boot-maven-plugin:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

打出来的 jar 包结构是这样的:

app.jar
├── BOOT-INF/
│   ├── classes/              # 项目自己的 class 文件
│   └── lib/                  # 所有依赖的 jar 包(被打平了)
│       ├── spring-boot-autoconfigure-3.2.0.jar
│       ├── spring-boot-3.2.0.jar
│       └── ...
└── org/
    └── springframework/
        └── boot/
            └── loader/
                └── JarLauncher.class   # 启动器

spring-boot-maven-plugin 做了两件事:

  1. 把所有依赖 jar 解压,将内容合并到一个扁平的 jar 中
  2. 添加一个 JarLauncher 类,用于加载 BOOT-INF/lib/ 下的依赖
💡

这就是为什么 Spring Boot 的 jar 可以用 java -jar app.jar 直接运行,而传统 Maven jar 必须指定 classpath。因为 Spring Boot 的 jar 本身就是可执行的。

3.2 JarLauncher 的类加载策略

// JarLauncher 核心逻辑
public class JarLauncher extends ExecutableArchiveLauncher {
    @Override
    protected boolean isNestedArchive(Archive.Entry entry) {
        // 只加载 BOOT-INF/classes 和 BOOT-INF/lib 下的内容
        return entry.getName().startsWith("BOOT-INF/");
    }
}

传统 Java 的类加载器只能加载文件系统上的 jar 文件。Spring Boot 的 JarLauncher 配合自定义的 LaunchedURLClassLoader,可以加载 jar 包内部的 jar(nested jar)。

四、Spring Boot 3.x 的变化 🟡

特性Spring Boot 2.xSpring Boot 3.x
自动配置声明META-INF/spring.factoriesMETA-INF/spring/org...AutoConfiguration.imports
JDK 最低版本JDK 8JDK 17
javax 迁移javax.*jakarta.*
嵌入式容器Tomcat 9 (javax.servlet)Tomcat 10 (jakarta.servlet)

Spring Boot 3.x 最大的变化是从 javax 命名空间迁移到了 jakarta。这意味着如果你引用了 Spring Boot 3.x 的 starter,所有依赖的 Servlet/JAXB 等都必须是 jakarta.* 版本。

<!-- Spring Boot 3.x -->
<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
</dependency>

<!-- Spring Boot 2.x -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
</dependency>

五、❌ 错误示范

5.1 把 Starter 当成框架

候选人原话:"spring-boot-starter-web 是一个框架,里面有 Spring MVC、Tomcat、Spring Boot 的核心代码。"

问题诊断

  • 不理解 Starter 的本质是依赖聚合,不是代码库
  • 不理解"自动配置"和"框架代码"的区别

面试官内心 OS:"这个候选人估计连 Maven dependency:tree 都没跑过。"

5.2 不知道自动配置在哪里

候选人原话:"Spring Boot 的自动配置是 Spring Boot 框架自己做的,不需要额外配置。"

问题诊断

  • 不知道 spring-boot-autoconfigure 的存在
  • 不知道自动配置类是有条件触发的
  • 不知道 spring.factories 或 imports 文件的作用

5.3 版本混淆

候选人原话:"Spring Boot 3.x 还是用 spring.factories,只是位置变了。"

问题诊断

  • 记混了 2.7 和 3.x 的区别
  • 2.7 开始引入了 imports,3.x 才完全移除了 spring.factories

六、标准回答

6.1 P5 级别

"Spring Boot Starter 是一组预定义的依赖集合。比如 spring-boot-starter-web 包含了 spring-boot-starter、spring-boot-starter-tomcat、spring-web、spring-webmvc 等依赖,让开发者只需要引入一个 starter 就能搭建 Web 项目。"

6.2 P6 级别

"Starter 本身不包含任何 Java 代码,它只是一个 pom.xml 定义了一组依赖坐标。真正的自动配置逻辑在 spring-boot-autoconfigure 包中,通过 spring.factories(2.x)或 AutoConfiguration.imports(3.x)声明。

Spring Boot 的启动依赖传递链路是:spring-boot-starter-web → spring-boot-starter → spring-boot-autoconfigure。spring-boot-autoconfigure 中的每个配置类通过 @Conditional 条件注解决定是否生效。

打出来的 jar 由 spring-boot-maven-plugin 打包成 fatjar 结构,包含 BOOT-INF/classes(项目代码)和 BOOT-INF/lib(所有依赖),由 JarLauncher 负责加载。"

6.3 P7 级别

"Starter 机制体现了 Spring Boot 的核心设计理念:'约定大于配置'。框架作者预先写好配置类,用户只需要引入 starter 依赖就能开箱即用。

我之前参与设计过公司的内部 starter,发现最大的坑是:starter 的 spring.factories 或 imports 文件必须由 spring-boot-autoconfigure 统一扫描。如果 starter 自己声明了重复的配置类,Spring Boot 2.7 之前可能会被全局扫描覆盖,2.7 之后收敛到了 imports 文件的范围内。

Spring Boot 3.x 的 jakarta 迁移是个大工程,我们当时升级时,Spring Security、Jackson 等所有引用 javax.servlet 的库全都要换成 jakarta 版本,光依赖排查就花了一周。"

【面试官心理】

P7 的回答重点在于"设计理念"和"踩坑经历"。知道 Starter 是依赖聚合只是基本操作,能说出"为什么这么设计"和"踩过什么坑"的才是真正有深度的候选人。