文章目录
-
- 1. 课前导读
- 2. 知识目标
- 3. 理论讲解
-
- 3.1 为什么Spring Boot 4.x要重构依赖体系
- 3.2 Maven依赖管理三层模型
- 3.3 Spring Boot 4.x模块化对依赖坐标的影响
- 3.4 Gradle配置的核心变化
- 4. 环境搭建
-
- 4.1 Maven方式一:继承spring-boot-starter-parent(推荐单模块项目)
- 4.2 Maven方式二:导入BOM(推荐多模块项目)
- 4.3 Maven多模块项目最佳实践
- 4.4 Gradle配置详解
- 4.5 使用版本目录(Version Catalog)
- 5. 代码实战
-
- 5.1 完整的多模块项目示例
- 5.2 运行与验证
- 5.3 依赖冲突排查实战
- 6. 底层源码剖析
-
- 6.1 spring-boot-starter-parent的继承机制
- 6.2 spring-boot-dependencies BOM的结构
- 6.3 spring-boot-maven-plugin的repackage目标
- 7. 常见坑点与生产问题排查
-
- 坑点1:spring-boot-starter-web 在4.x中已重命名
- 坑点2:多模块项目中父POM误用plugins导致repackage全家桶
- 坑点3:BOM导入后仍需要手动指定版本
- 坑点4:Jackson 3包名迁移导致自定义配置编译失败
- 坑点5:Gradle版本过低导致插件不兼容
- 坑点6:第三方Starter未适配4.x模块化
- 8. 本章总结
- 9. 课后作业
-
- 基础题1
- 基础题2
- 综合实战题
- 🔗 SpringBoot 4.x 从入门到企业级实战系列课程

本文是《SpringBoot 4.x 从入门到精通》系列第2课,全文约13000字,建议阅读时间50分钟。 环境要求:Spring Boot 4.x,Spring Framework 7,Java 17+(推荐Java 21/25)
1. 课前导读
上一课我们完成了Spring Boot 4.x的HelloWorld程序,相信你已经感受到了4.x的“开箱即用”。但你有没有想过一个问题:为什么pom.xml里不写版本号也能正常工作?为什么spring-boot-starter-webmvc能自动引入Tomcat、Spring MVC、Jackson等一系列依赖?
答案就在项目构建和依赖管理这两个核心机制里。
Spring Boot 4.0对项目构建体系做了自3.0以来最大的一次重构。spring-boot-starter-web被重命名为spring-boot-starter-webmvc,单一的spring-boot-autoconfigure模块被拆分为多个聚焦的小模块,每个模块都有独立的starter和test-starter。这意味着依赖坐标变了、依赖树变了、父工程的用法也变了。
如果你的企业项目中有多模块架构,或者你有自定义的父工程需要统一管理版本,这节课的内容将直接决定你的项目能否顺利迁移到4.x。
前置知识:Maven或Gradle基本使用、了解dependencyManagement和dependencies的区别。
学习难点:理解Spring Boot 4.x模块化对依赖坐标的影响;掌握父工程+BOM+Starter的正确分工;区分spring-boot-starter-parent与spring-boot-dependencies的使用场景。
【配图描述:Spring Boot 4.x 依赖管理三层模型图——Parent(规则继承)、BOM(版本管理)、Starter(功能引入)】
2. 知识目标
学完本节课,你必须掌握以下核心知识点:
3. 理论讲解
3.1 为什么Spring Boot 4.x要重构依赖体系
在理解4.x的依赖管理之前,我们需要先搞清楚一个根本问题:Spring Boot 3.x的依赖体系有什么问题?
Spring Boot 3.x中,所有的自动配置类都集中在一个约2MB的spring-boot-autoconfigure JAR包中。无论你的应用是Web应用、批处理应用还是消息驱动应用,这个JAR包都会被完整加载到classpath中。类加载器需要扫描这个JAR包中所有的AutoConfiguration.imports文件,然后根据条件注解逐一判断哪些配置类需要生效。
这带来了三个问题:
启动开销。 即使你的应用只用了Web和JDBC,JPA、Security、Redis、Kafka等模块的自动配置类仍然会被加载到JVM中,只是不生效而已。类加载和条件判断都消耗了启动时间。
内存浪费。 未使用的自动配置类及其依赖的类仍然占用元空间(Metaspace)内存。
模块耦合。 第三方Starter如果想提供自动配置,必须依赖整个spring-boot-autoconfigure模块,即使它只需要其中的一两个注解。
Spring Boot 4.0的模块化重构正是为了解决这些问题。将spring-boot-autoconfigure拆分为spring-boot-webmvc、spring-boot-jdbc、spring-boot-data-jpa等多个聚焦模块后,应用只加载实际引入的模块中的自动配置类。
【配图描述:Spring Boot 3.x单体autoconfigure vs 4.x模块化autoconfigure的类加载范围对比图】
3.2 Maven依赖管理三层模型
Spring Boot的Maven依赖管理可以抽象为三个层次,理解这个模型是掌握4.x项目构建的关键。
第一层:Parent(规则继承)。 spring-boot-starter-parent是Spring Boot提供的“约定优于配置”的体现。它定义了Java编译版本、UTF-8编码、资源过滤规则、插件配置等构建规范。继承Parent意味着你的项目自动获得了这些默认配置,不需要在每个项目中重复编写。
第二层:BOM(版本管理)。 spring-boot-dependencies是一个BOM(Bill of Materials),只做一件事:管理依赖的版本。它不引入任何JAR包,只是声明了“如果使用这个依赖,应该用哪个版本”。Parent内部自动导入了这个BOM,所以继承Parent的项目可以省略<version>标签。
第三层:Starter(功能引入)。 spring-boot-starter-webmvc等Starter才是真正“引入JAR包并启用功能”的地方。一个Starter通常会引入多个依赖(如Spring MVC、Tomcat、Jackson)并触发相应的自动配置。
这三层的关系可以用一句话概括:Parent管规则,BOM管版本,Starter管功能。在Spring Boot 4.x的多模块项目中,推荐的做法是——父POM通过dependencyManagement导入BOM来管理版本,子模块依赖Starter来启用功能,插件放在pluginManagement中按需启用。
3.3 Spring Boot 4.x模块化对依赖坐标的影响
这是4.x项目构建中最容易踩坑的地方。模块化重构直接改变了Starter的坐标名称。
Web模块的变化。 在3.x中,Web项目依赖spring-boot-starter-web。在4.x中,这个Starter被重命名为spring-boot-starter-webmvc。对应的测试依赖也从spring-boot-starter-test变为spring-boot-starter-webmvc-test。
Test Starter的独立化。 3.x中有一个统一的spring-boot-starter-test,包含JUnit、Mockito、AssertJ等所有测试工具。4.x中,每个功能模块都有独立的test-starter,例如spring-boot-starter-webmvc-test、spring-boot-starter-data-jpa-test、spring-boot-starter-security-test。这意味着测试依赖更加精准——测试Web层不需要引入JPA的测试工具。
Classic Starter过渡方案。 为了兼容大量现有项目,Spring Boot 4.0提供了“Classic Starter POMs”。这些Classic Starter包含对应模块的所有依赖,但排除了所有传递依赖。如果你在升级现有项目时不想逐个调整依赖坐标,可以暂时使用Classic Starter,但这只是一种过渡方案。
【配图描述:Spring Boot 3.x vs 4.x Starter坐标对照表】
3.4 Gradle配置的核心变化
Gradle用户同样需要关注4.x的变化。Spring Boot 4.x的Gradle插件版本同步升级到4.x,要求Gradle 7.6.4+或8.4+。
在Gradle中,Spring Boot的依赖管理有两种方式:
方式一:使用io.spring.dependency-management插件。 这是传统方式,插件会自动导入Spring Boot的BOM,使得依赖声明可以省略版本号。
方式二:使用platform()导入BOM。 这是Gradle原生方式,通过implementation platform("org.springframework.boot:spring-boot-dependencies:4.0.6")来导入BOM。Spring Boot官方推荐在新项目中采用这种方式,因为它不依赖额外插件,更加透明。
此外,Gradle用户应该关注版本目录(Version Catalog) 的用法。4.x的模块化使得每个功能模块有独立的starter坐标,使用版本目录可以在libs.versions.toml中统一定义所有Starter别名,避免在build.gradle中硬编码坐标。
4. 环境搭建
4.1 Maven方式一:继承spring-boot-starter-parent(推荐单模块项目)
这是最简单的方式,适用于单模块项目。
<?xml version="1.0" encoding="UTF-8"?>
<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>
<!– 继承 Spring Boot 4.x Parent,获得构建默认配置 –>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.8</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>single-module-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>single-module-app</name>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<!– Spring Boot 4.x Web MVC Starter(替代3.x的starter-web) –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<!– 测试依赖(4.x独立化的test-starter) –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
关键点:继承Parent后,spring-boot-starter-webmvc和spring-boot-starter-webmvc-test都不需要写<version>标签。Parent自动导入了spring-boot-dependencies BOM,所有Spring Boot管理的依赖版本都由BOM决定。
4.2 Maven方式二:导入BOM(推荐多模块项目)
如果你的企业已有自己的父工程,不能继承spring-boot-starter-parent,可以使用BOM导入方式。
<?xml version="1.0" encoding="UTF-8"?>
<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>
<!– 企业自有父工程 –>
<parent>
<groupId>com.example</groupId>
<artifactId>company-parent</artifactId>
<version>2.0.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>standalone-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<java.version>21</java.version>
<spring-boot.version>4.0.8</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<!– 导入 Spring Boot BOM,只管理版本,不引入依赖 –>
<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-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
注意:使用这种方式时,需要手动配置spring-boot-maven-plugin的版本(通常放在父工程的pluginManagement中统一管理)。同时,Java编译版本、UTF-8编码等构建配置也需要手动声明,因为不再继承Spring Boot Parent。
4.3 Maven多模块项目最佳实践
这是企业项目中最重要的配置模式。假设你有一个多模块项目,结构如下:
company-project/
├── pom.xml (父POM:只做版本管理和插件管理)
├── common/ (通用模块:不含Spring Boot功能)
│ └── pom.xml
├── api/ (API模块:DTO和接口定义)
│ └── pom.xml
└── web/ (Web模块:Spring Boot应用)
└── pom.xml
父POM(company-project/pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<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>company-project</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>common</module>
<module>api</module>
<module>web</module>
</modules>
<properties>
<java.version>21</java.version>
<spring-boot.version>4.0.8</spring-boot.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<!– 导入 Spring Boot BOM –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!– 内部模块版本管理 –>
<dependency>
<groupId>com.example</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<!– 插件放在 pluginManagement 中,子模块按需启用 –>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>${java.version}</release>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
Web子模块(web/pom.xml)
<project …>
<parent>
<groupId>com.example</groupId>
<artifactId>company-project</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>web</artifactId>
<dependencies>
<!– 内部模块依赖 –>
<dependency>
<groupId>com.example</groupId>
<artifactId>common</artifactId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>api</artifactId>
</dependency>
<!– Spring Boot Starter(版本由父POM的BOM管理) –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!– 只有web模块需要打包成可执行JAR –>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
核心原则:父POM用BOM定版本,子模块用Starter启功能,插件放在pluginManagement中按需启用。父POM不应该放在<plugins>中,否则所有子模块(包括common和api)都会执行repackage,导致“全家一起打包成Boot应用”的事故。
4.4 Gradle配置详解
build.gradle(Groovy DSL)
plugins {
id 'java'
id 'org.springframework.boot' version '4.0.8'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'com.example'
version = '1.0.0-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
// Spring Boot 4.x Web MVC Starter
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
// 测试 Starter
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
}
tasks.named('test') {
useJUnitPlatform()
}
Gradle原生BOM导入方式(推荐)
如果你不想使用io.spring.dependency-management插件,可以使用Gradle原生的platform():
plugins {
id 'java'
id 'org.springframework.boot' version '4.0.8'
}
dependencies {
// 导入 Spring Boot BOM
implementation platform('org.springframework.boot:spring-boot-dependencies:4.0.8')
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
}
Gradle多模块项目配置
根项目settings.gradle:
rootProject.name = 'company-project'
include 'common'
include 'api'
include 'web'
根项目build.gradle:
plugins {
id 'java' apply false
id 'org.springframework.boot' version '4.0.8' apply false
}
subprojects {
apply plugin: 'java'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation platform('org.springframework.boot:spring-boot-dependencies:4.0.8')
}
}
web/build.gradle:
plugins {
id 'org.springframework.boot'
}
dependencies {
implementation project(':common')
implementation project(':api')
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
}
4.5 使用版本目录(Version Catalog)
Gradle 7.0+引入了版本目录特性,在Spring Boot 4.x的多模块项目中尤为有用。
gradle/libs.versions.toml:
[versions]
spring-boot = "4.0.8"
java = "21"
[libraries]
spring-boot-webmvc = { module = "org.springframework.boot:spring-boot-starter-webmvc" }
spring-boot-webmvc-test = { module = "org.springframework.boot:spring-boot-starter-webmvc-test" }
[plugins]
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
build.gradle中使用:
plugins {
alias(libs.plugins.spring.boot)
}
dependencies {
implementation platform("org.springframework.boot:spring-boot-dependencies:${libs.versions.spring.boot.get()}")
implementation libs.spring.boot.webmvc
testImplementation libs.spring.boot.webmvc.test
}
5. 代码实战
5.1 完整的多模块项目示例
让我们创建一个完整的可运行多模块项目,包含通用工具模块、API模块和Web模块。
项目结构
multi-module-demo/
├── pom.xml
├── common/
│ ├── pom.xml
│ └── src/main/java/com/example/common/
│ └── util/StringUtils.java
├── api/
│ ├── pom.xml
│ └── src/main/java/com/example/api/
│ └── dto/UserDTO.java
└── web/
├── pom.xml
└── src/main/
├── java/com/example/web/
│ ├── MultiModuleApplication.java
│ └── controller/UserController.java
└── resources/application.yml
common模块的StringUtils.java
package com.example.common.util;
/**
* 通用字符串工具类。
* 该模块不依赖 Spring Boot,只包含纯 Java 逻辑。
*/
public final class StringUtils {
private StringUtils() {}
/**
* 判断字符串是否为空白(null、空串、仅空格)。
*/
public static boolean isBlank(String str) {
return str == null || str.trim().isEmpty();
}
/**
* 反转字符串,null 安全。
*/
public static String reverse(String str) {
if (str == null) return null;
return new StringBuilder(str).reverse().toString();
}
}
api模块的UserDTO.java
package com.example.api.dto;
import java.time.LocalDateTime;
/**
* 用户数据传输对象。
* 该模块只包含 DTO 定义,被 web 模块引用。
*/
public class UserDTO {
private Long id;
private String name;
private String email;
private LocalDateTime createdAt;
// 无参构造器
public UserDTO() {}
public UserDTO(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
this.createdAt = LocalDateTime.now();
}
// getter / setter 省略,实际代码中需完整实现
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
}
web模块的启动类
package com.example.web;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 多模块 Web 应用启动入口。
*
* 注意:@SpringBootApplication 的扫描范围是 com.example.web 及子包。
* common 和 api 模块的类需要通过 @Import 或 @ComponentScan 显式引入。
*/
@SpringBootApplication
public class MultiModuleApplication {
public static void main(String[] args) {
SpringApplication.run(MultiModuleApplication.class, args);
}
}
web模块的Controller
package com.example.web.controller;
import com.example.api.dto.UserDTO;
import com.example.common.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* 用户管理接口,演示多模块依赖。
*/
@RestController
@RequestMapping("/api/users")
public class UserController {
private final ConcurrentHashMap<Long, UserDTO> store = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(1);
/**
* 创建用户。
* POST /api/users
* Body: {"name":"张三","email":"zhangsan@example.com"}
*/
@PostMapping
public UserDTO create(@RequestBody UserDTO user) {
if (StringUtils.isBlank(user.getName())) {
throw new IllegalArgumentException("用户名不能为空");
}
long id = idGenerator.getAndIncrement();
UserDTO saved = new UserDTO(id, user.getName(), user.getEmail());
store.put(id, saved);
return saved;
}
/**
* 获取所有用户。
* GET /api/users
*/
@GetMapping
public List<UserDTO> list() {
return List.copyOf(store.values());
}
/**
* 根据 ID 获取用户。
* GET /api/users/{id}
*/
@GetMapping("/{id}")
public UserDTO getById(@PathVariable Long id) {
UserDTO user = store.get(id);
if (user == null) {
throw new IllegalArgumentException("用户不存在: " + id);
}
return user;
}
/**
* 演示 StringUtils 的使用。
* GET /api/users/reverse?input=hello
*/
@GetMapping("/reverse")
public String reverse(@RequestParam String input) {
return StringUtils.reverse(input);
}
}
5.2 运行与验证
编译整个项目
cd multi-module-demo
mvn clean install
启动Web模块
cd web
mvn spring-boot:run
测试接口
# 创建用户
curl -X POST http://localhost:8080/api/users \\
-H "Content-Type: application/json" \\
-d '{"name":"张三","email":"zhangsan@example.com"}'
# 输出: {"id":1,"name":"张三","email":"zhangsan@example.com","createdAt":"2025-12-01T10:30:00"}
# 获取所有用户
curl http://localhost:8080/api/users
# 输出: [{"id":1,"name":"张三","email":"zhangsan@example.com","createdAt":"…"}]
# 字符串反转
curl "http://localhost:8080/api/users/reverse?input=hello"
# 输出: olleh
5.3 依赖冲突排查实战
假设项目中出现了一个依赖冲突场景:common模块引入了commons-lang3:3.12.0,而Spring Boot BOM管理的是3.14.0。运行mvn dependency:tree可以看到:
[INFO] com.example:web:jar:1.0.0-SNAPSHOT
[INFO] +- com.example:common:jar:1.0.0-SNAPSHOT
[INFO] | \\- org.apache.commons:commons-lang3:jar:3.12.0:compile
[INFO] +- org.springframework.boot:spring-boot-starter-webmvc:jar:4.0.8:compile
[INFO] | \\- (org.apache.commons:commons-lang3:jar:3.14.0:compile – omitted for duplicate)
由于Maven的“最近优先”规则,commons-lang3:3.12.0被选中,与Spring Boot管理的版本不一致。解决方案是在父POM中覆盖BOM属性:
<properties>
<commons-lang3.version>3.14.0</commons-lang3.version>
</properties>
或者在dependencyManagement中显式声明:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
</dependencies>
</dependencyManagement>
6. 底层源码剖析
6.1 spring-boot-starter-parent的继承机制
spring-boot-starter-parent的pom.xml本身非常简洁,它的核心作用是通过继承链将构建配置传递给子项目。
<!– spring-boot-starter-parent-4.0.8.pom 核心内容 –>
<project>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>4.0.8</version>
<relativePath>../spring-boot-dependencies</relativePath>
</parent>
<artifactId>spring-boot-starter-parent</artifactId>
<packaging>pom</packaging>
<properties>
<java.version>17</java.version>
<maven.compiler.release>${java.version}</maven.compiler.release>
<resource.delimiter>@</resource.delimiter>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<pluginManagement>
<plugins>
<!– 配置 spring-boot-maven-plugin 的默认执行 –>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
关键点:spring-boot-starter-parent继承了spring-boot-dependencies,因此子项目继承Parent后自动获得了所有依赖版本管理。同时,Parent在pluginManagement中定义了spring-boot-maven-plugin的默认执行(repackage),子项目只需要在<plugins>中声明插件即可触发可执行JAR打包。
6.2 spring-boot-dependencies BOM的结构
spring-boot-dependencies是一个纯粹的BOM,它的结构如下:
<project>
<artifactId>spring-boot-dependencies</artifactId>
<packaging>pom</packaging>
<properties>
<spring-framework.version>7.0.9</spring-framework.version>
<jackson-bom.version>3.0.0</jackson-bom.version>
<tomcat.version>11.0.0</tomcat.version>
<hikaricp.version>7.0.0</hikaricp.version>
<!– … 数百个版本属性 –>
</properties>
<dependencyManagement>
<dependencies>
<!– Spring Framework BOM –>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring-framework.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!– Jackson 3 BOM –>
<dependency>
<groupId>tools.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>${jackson-bom.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!– Spring Boot Starter 的版本声明 –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
<version>${project.version}</version>
</dependency>
<!– … –>
</dependencies>
</dependencyManagement>
</project>
<dependencyManagement>中的依赖声明不会引入JAR包,只是告诉Maven“如果项目使用了这个依赖,应该用这个版本”。当子项目的<dependencies>中声明了spring-boot-starter-webmvc且没有指定版本时,Maven会从dependencyManagement中查找版本。
6.3 spring-boot-maven-plugin的repackage目标
spring-boot-maven-plugin的核心功能是repackage目标,它将Maven生成的普通JAR重新打包为可执行的Spring Boot JAR。在4.x中,repackage的执行流程与3.x基本一致:
// 源码路径:org.springframework.boot.maven.RepackageMojo
public class RepackageMojo extends AbstractPackagerMojo {
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
// 1. 获取原始 JAR 文件
File source = this.project.getArtifact().getFile();
// 2. 创建可执行 JAR
File target = getTargetFile();
// 3. 执行重新打包
repackage(source, target);
// 4. 替换原始 JAR
// …
}
}
4.x的关键变化在于分层打包(Layered JAR)的默认启用。分层打包将应用代码和依赖库分离到不同的层中,Docker构建时可以复用依赖层,显著加快镜像构建速度。分层信息写入JAR中的layers.idx文件。
7. 常见坑点与生产问题排查
坑点1:spring-boot-starter-web 在4.x中已重命名
报错现象:使用spring-boot-starter-web时IDE提示依赖已废弃,或无法找到某些自动配置类。
原因:Spring Boot 4.0将spring-boot-starter-web重命名为spring-boot-starter-webmvc,对应的测试Starter也从spring-boot-starter-test变为spring-boot-starter-webmvc-test。
解决方案:将依赖坐标改为spring-boot-starter-webmvc和spring-boot-starter-webmvc-test。
坑点2:多模块项目中父POM误用plugins导致repackage全家桶
报错现象:common和api模块也被打包成可执行JAR,启动时报“no main manifest attribute”。
原因:spring-boot-maven-plugin被放在父POM的<plugins>中(而非<pluginManagement>),导致所有子模块都继承了repackage的执行。
解决方案:将插件移到父POM的<pluginManagement>中,只在需要打包成Boot应用的子模块(如web)的<plugins>中声明。
坑点3:BOM导入后仍需要手动指定版本
报错现象:导入spring-boot-dependencies BOM后,依赖仍然报“version is missing”。
原因:<scope>import</scope>必须配合<type>pom</type>使用,且必须放在<dependencyManagement>中。如果放在<dependencies>中,BOM本身会被当作依赖引入,但版本管理不生效。
解决方案:确保BOM依赖声明在<dependencyManagement>中,包含<type>pom</type>和<scope>import</scope>。
坑点4:Jackson 3包名迁移导致自定义配置编译失败
报错现象:import com.fasterxml.jackson.databind.ObjectMapper;编译失败。
原因:Spring Boot 4.0默认使用Jackson 3,包名从com.fasterxml.jackson迁移到tools.jackson。
解决方案:将自定义Jackson配置的import改为tools.jackson.databind.ObjectMapper。
坑点5:Gradle版本过低导致插件不兼容
报错现象:Plugin [id: 'org.springframework.boot', version: '4.0.8'] was not found。
原因:Spring Boot 4.x的Gradle插件要求Gradle 7.6.4+或8.4+。
解决方案:升级Gradle版本,或在gradle/wrapper/gradle-wrapper.properties中修改distributionUrl。
坑点6:第三方Starter未适配4.x模块化
报错现象:引入第三方Starter(如某些国产ORM框架的Spring Boot Starter)后,自动配置不生效。
原因:第三方Starter可能依赖了spring-boot-autoconfigure的内部类,而这些类在4.x中变为package-private或移动了位置。
解决方案:检查第三方Starter是否已发布4.x兼容版本;如果还没有,暂时使用Classic Starter过渡,或联系维护者更新。
8. 本章总结
本节课围绕Spring Boot 4.x的项目构建和依赖管理,从Maven和Gradle两个维度构建了完整的知识体系。
Maven三层模型:Parent管规则(构建配置、插件默认值),BOM管版本(spring-boot-dependencies),Starter管功能(引入依赖和自动配置)。单模块项目推荐继承spring-boot-starter-parent,多模块项目推荐父POM导入BOM + 子模块依赖Starter。
4.x模块化变化:spring-boot-starter-web→spring-boot-starter-webmvc,Test Starter独立化为各模块的-test后缀,Classic Starter作为过渡方案保留。自动配置类从单一spring-boot-autoconfigure模块分散到各功能模块中。
Gradle配置:Spring Boot 4.x插件要求Gradle 7.6.4+,推荐使用platform()导入BOM,版本目录(Version Catalog)在多模块项目中管理Starter坐标。
多模块最佳实践:父POM用BOM定版本,子模块用Starter启功能,插件放在pluginManagement中按需启用。父POM不应放在<plugins>中,避免“全家桶repackage”事故。
9. 课后作业
基础题1
创建一个单模块Spring Boot 4.x项目,分别使用以下两种方式管理依赖版本:
- 方式A:继承spring-boot-starter-parent
- 方式B:导入spring-boot-dependencies BOM
对比两种方式的pom.xml差异,运行mvn dependency:tree,记录输出结果,解释为什么方式A可以省略spring-boot-maven-plugin的版本号。
基础题2
使用Gradle创建一个Spring Boot 4.x项目,分别使用io.spring.dependency-management插件和platform()导入BOM。观察gradle dependencies输出的差异,记录BOM是否被正确导入。
综合实战题
创建一个完整的多模块Maven项目,包含以下三个模块:
- POST /api/orders:创建订单,返回OrderDTO
- GET /api/orders:返回所有订单列表
- GET /api/orders/{id}/time:返回订单创建时间的格式化字符串(使用DateUtils)
要求:
- 父POM使用BOM导入方式管理Spring Boot版本
- 父POM的插件配置放在pluginManagement中
- 只有web模块声明spring-boot-maven-plugin在<plugins>中
- 使用mvn clean install编译整个项目
- 使用mvn dependency:tree检查依赖树,确保没有版本冲突
- 启动web模块,使用curl测试所有接口
扩展挑战:在父POM中覆盖commons-lang3的版本为3.14.0,在common模块中添加一个使用StringUtils.isBlank()的方法,验证版本覆盖是否生效。
🔗 SpringBoot 4.x 从入门到企业级实战系列课程
去订阅
网硕互联帮助中心





评论前必须登录!
注册