国内90%的Java开发者都踩过SpringBoot版本升级的坑——要么盲目升级到3.x后项目启动直接报错,要么只知道换个版本号却完全没用到3.x的核心能力,甚至因为不了解底层差异导致线上出现性能故障。
SpringBoot 2和3不是简单的版本号迭代,而是从底层JDK基线、EE规范、架构设计到云原生能力的全面重构,两者的核心差异直接决定了项目的技术选型、升级成本与长期维护性。
版本基线与底层规范的本质差异
两个版本的所有差异,都源于底层基线与规范的根本性变更,这也是升级前必须先搞懂的核心逻辑。
JDK版本基线的硬门槛
SpringBoot 3.0.0正式发布于2022年11月,最低运行要求为JDK 17 LTS,后续的3.2.x版本还全面支持JDK 21 LTS的虚拟线程等核心特性。而SpringBoot 2.x的最终维护版本为2.7.x,最低兼容JDK 8,最高可适配JDK 21,但核心能力完全基于JDK 8的语法与API设计,无法深度利用高版本JDK的底层优化。
JDK 17带来的不仅是语法糖,更是JVM层面的革命性优化:分代ZGC垃圾回收器的停顿时间控制在亚毫秒级,彻底解决了大堆内存的GC停顿问题;密封类、record类型、模式匹配等新语法,能大幅简化代码、提升可读性与安全性;JDK 21的虚拟线程,更是彻底颠覆了Java并发编程的模式。这些特性只有SpringBoot 3能原生深度支持,SpringBoot 2受限于JDK基线,完全无法发挥这些能力。
Jakarta EE 9+规范的全面迁移
这是两个版本之间最大的破坏性变更,也是90%开发者升级踩的第一个坑。
Oracle在2017年将Java EE捐献给Eclipse基金会,但保留了javax.*包名的商标所有权,Eclipse基金会只能将整个EE规范的包名全面替换为jakarta.*。从Jakarta EE 9版本开始,所有企业级Java规范的包名完成全面切换,而SpringBoot 3基于Spring Framework 6,完全适配Jakarta EE 9+规范,SpringBoot 2则基于Spring Framework 5,完全基于javax.*的Java EE 8规范设计。
核心包名的替换对比如下:
| 旧包名(SpringBoot 2) | 新包名(SpringBoot 3) |
| javax.servlet | jakarta.servlet |
| javax.annotation | jakarta.annotation |
| javax.transaction | jakarta.transaction |
| javax.persistence | jakarta.persistence |
| javax.validation | jakarta.validation |
| javax.jms | jakarta.jms |
| javax.websocket | jakarta.websocket |
很多开发者以为这只是简单的全局替换,实际远不止于此。包名变更意味着整个Java EE生态的全面重构,所有依赖EE规范的第三方组件,都必须发布适配jakarta.*的新版本才能在SpringBoot 3中运行。比如Tomcat 10.1.x才支持jakarta.servlet规范,Tomcat 9.x及以下版本仅支持javax.servlet;老版本的Dubbo、RocketMQ、Swagger2等组件,没有适配Jakarta EE规范,直接在SpringBoot 3中引入会导致整个项目的依赖体系崩溃。这也是很多企业级项目至今仍停留在SpringBoot 2.7.x的核心原因。
Spring Framework版本的强绑定
SpringBoot的核心能力完全依赖底层的Spring Framework,两个版本的Spring Framework绑定关系完全不可拆分:
- SpringBoot 3.x 完全绑定 Spring Framework 6.x,该版本基于JDK 17和Jakarta EE规范全面重构,移除了所有在5.x版本中标记为废弃的API,只做新特性开发与安全维护。
- SpringBoot 2.x 完全绑定 Spring Framework 5.x,最终版本为5.3.x,已于2024年停止新特性开发,仅提供关键安全漏洞的修复,不再进行任何功能迭代。
Spring Framework 6.x的重构,不仅是包名的替换,更是对Bean生命周期、依赖注入、AOP、事务管理等核心模块的全面优化,减少了运行时反射的使用,为AOT编译与原生镜像提供了底层支持,这些都是Spring Framework 5.x无法实现的。
核心架构与运行时的底层升级差异
SpringBoot 3对整个运行时架构做了革命性重构,核心能力的差异直接决定了项目的启动速度、内存占用、并发能力与云原生适配性。
AOT提前编译与GraalVM原生镜像的正式支持
SpringBoot 2.x时代,Spring Native只是一个单独的实验性项目,功能不完善,适配性差,几乎无法在生产环境使用。而SpringBoot 3.x将AOT提前编译与GraalVM原生镜像能力完全内置到了核心框架中,成为官方主推的核心特性。
JVM的JIT即时编译是在程序运行时,对热点代码进行编译优化,优势是峰值性能高,但缺点是启动速度慢,内存占用高,需要预热才能达到最佳性能,非常不适合云原生Serverless场景的短生命周期应用。而AOT提前编译是在程序构建阶段,就对Java代码进行提前处理,生成Bean定义的字节码、代理类、反射元数据与GraalVM原生镜像所需的配置文件,完全消除了运行时的反射与动态代理开销。
AOT编译的完整流程如下:
通过AOT编译与GraalVM原生镜像构建,SpringBoot 3应用可以编译为平台相关的可执行二进制文件,启动速度可以提升10倍以上,内存占用降低50%以上,无需预热即可达到峰值性能,完美适配云原生Serverless、边缘计算等场景。而SpringBoot 2完全依赖运行时的反射与动态代理,无法实现深度的AOT优化,原生镜像支持仅停留在实验阶段。
虚拟线程的原生深度支持
虚拟线程是JDK 21 LTS正式发布的革命性特性,彻底改变了Java并发编程的模式。SpringBoot 3.2+版本对虚拟线程做了全场景的原生支持,只需要在配置文件中添加一行配置spring.threads.virtual.enabled=true,即可将Tomcat的请求处理线程、Spring MVC的异步线程、@Async注解的异步线程池、定时任务线程池全部替换为虚拟线程,无需修改任何业务代码。
虚拟线程是JVM层面实现的轻量级线程,调度完全由JVM管理,不占用操作系统的内核线程,创建成本几乎为零,并发量可以轻松达到几十万级别。而传统的平台线程,每个线程都对应一个操作系统内核线程,内存占用大,创建与切换成本高,单机并发量最多只能达到几千级别。SpringBoot 2受限于JDK基线,完全无法支持虚拟线程,高并发场景下只能通过优化平台线程池来提升并发能力,上限非常明显。
需要注意的是,虚拟线程的优势在于处理大量IO密集型任务,比如数据库查询、远程接口调用、文件读写等场景,对于CPU密集型任务,虚拟线程并不能带来性能提升。如果在虚拟线程中使用synchronized锁或者长时间的阻塞操作,会导致虚拟线程无法被JVM卸载,完全失去轻量级的优势,推荐使用ReentrantLock替代synchronized锁。
响应式编程的全面升级
SpringBoot 3.x的Spring WebFlux 6.x做了全面重构,升级到Reactor 3.5+版本,实现了与虚拟线程的深度集成,同时完善了响应式事务、响应式安全、响应式缓存的全场景支持。响应式编程的核心优势是完全非阻塞,资源利用率极高,但之前的痛点是开发门槛高、调试难度大,而虚拟线程的集成,让开发者可以用同步的代码写法,实现非阻塞的性能,大幅降低了响应式编程的使用门槛。
SpringBoot 2.x的WebFlux 2.7.x版本,对响应式编程的支持有限,很多特性需要额外的适配开发,且只能基于平台线程运行,非阻塞的优势无法完全发挥,在实际项目中的使用率非常低。
Bean生命周期与配置绑定的优化
Spring Framework 6.x对Bean的实例化与生命周期管理做了全面重构,在AOT编译阶段就完成了Bean定义的解析、代理类的生成,运行时直接实例化Bean,大幅减少了反射的使用,启动速度提升明显。而Spring Framework 5.x完全依赖运行时的反射来解析Bean定义、实例化Bean、生成动态代理,启动过程中会产生大量的反射开销。
同时,SpringBoot 3对JDK 17的record类型做了完美支持,@ConfigurationProperties注解可以直接绑定到record类上,无需编写setter方法,代码更加简洁,不可变对象的特性也提升了线程安全性。示例如下:
package com.jam.demo.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 应用配置绑定类
* @author ken
*/
@ConfigurationProperties(prefix = "app.config")
public record AppConfigProperties(
String appName,
String version,
Integer maxConnection,
Long timeout
) {
}
而SpringBoot 2对record类型的支持非常有限,仅在高版本中做了基础适配,很多场景下无法正常绑定,只能使用传统的Java Bean类,编写大量的getter/setter方法。
Web开发全场景的功能差异
Web开发是Java开发者日常工作中最核心的场景,两个版本在Web开发的功能、API、适配性上有非常多的差异,直接影响日常开发效率。
声明式HTTP客户端@HttpExchange的原生支持
Spring Framework 6.x将@HttpExchange声明式HTTP客户端正式纳入核心框架,SpringBoot 3原生支持该特性,无需引入任何额外依赖。开发者只需要定义一个接口,加上对应的HTTP注解,即可实现声明式的HTTP远程调用,完全替代传统的RestTemplate和WebClient,代码更加简洁,可读性更强。
完整的使用示例如下:
package com.jam.demo.api;
import com.jam.demo.entity.User;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
/**
* 用户远程HTTP接口声明
* @author ken
*/
@HttpExchange(url = "/api/remote/user")
public interface UserHttpApi {
/**
* 根据用户ID查询用户信息
* @param id 用户ID
* @return 用户信息
*/
@GetExchange("/{id}")
User getUserById(@PathVariable("id") Long id);
/**
* 保存用户信息
* @param user 用户实体
* @return 保存结果
*/
@PostExchange("/save")
Boolean saveUser(@RequestBody User user);
}
package com.jam.demo.config;
import com.jam.demo.api.UserHttpApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
/**
* HTTP接口代理配置类
* @author ken
*/
@Configuration
public class HttpApiConfig {
@Bean
public UserHttpApi userHttpApi() {
RestClient restClient = RestClient.create("https://api.example.com");
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
return factory.createClient(UserHttpApi.class);
}
}
配置完成后,直接在业务代码中注入UserHttpApi即可使用,无需编写任何HTTP调用的模板代码。而SpringBoot 2仅在高版本中提供了实验性的@HttpExchange支持,需要额外引入spring-webflux依赖,且功能不完善,存在很多兼容性问题,实际项目中几乎无法使用。
OpenAPI 3(Swagger3)的唯一支持
SpringBoot 3完全不支持老版本的SpringFox(Swagger2),因为SpringFox底层依赖javax.servlet包,且已经停止维护多年,没有发布适配Jakarta EE规范的版本。很多开发者升级SpringBoot 3后,遇到的第一个问题就是Swagger页面无法打开,根源就是使用了不兼容的SpringFox依赖。
SpringBoot 3唯一支持的接口文档规范是OpenAPI 3,对应的实现是springdoc-openapi,也就是常说的Swagger3。完整的使用示例如下:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
package com.jam.demo.config;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Contact;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.info.License;
import org.springframework.context.annotation.Configuration;
/**
* OpenAPI文档配置类
* @author ken
*/
@Configuration
@OpenAPIDefinition(
info = @Info(
title = "用户管理系统API文档",
version = "1.0.0",
description = "用户管理系统的后端接口文档",
contact = @Contact(name = "开发团队", email = "dev@example.com"),
license = @License(name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0")
)
)
public class OpenApiConfig {
}
package com.jam.demo.controller;
import com.jam.demo.entity.User;
import com.jam.demo.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import java.util.List;
/**
* 用户管理控制器
* @author ken
*/
@Slf4j
@RestController
@RequestMapping("/api/user")
@Tag(name = "用户管理", description = "用户信息的增删改查接口")
public class UserController {
@Resource
private UserService userService;
@GetMapping("/{id}")
@Operation(
summary = "根据ID查询用户",
description = "根据用户ID查询用户详细信息",
parameters = @Parameter(name = "id", description = "用户ID", required = true, example = "1"),
responses = {
@ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = User.class))),
@ApiResponse(responseCode = "400", description = "参数错误"),
@ApiResponse(responseCode = "404", description = "用户不存在")
}
)
public User getUserById(@PathVariable Long id) {
if (ObjectUtils.isEmpty(id)) {
throw new IllegalArgumentException("用户ID不能为空");
}
return userService.getById(id);
}
@PostMapping("/save")
@Operation(summary = "保存用户", description = "新增用户信息")
public Boolean saveUser(@RequestBody User user) {
return userService.saveUser(user);
}
@PutMapping("/update")
@Operation(summary = "更新用户", description = "修改用户信息")
public Boolean updateUser(@RequestBody User user) {
return userService.updateUser(user);
}
@DeleteMapping("/{id}")
@Operation(summary = "删除用户", description = "根据用户ID逻辑删除用户")
public Boolean deleteUser(@PathVariable Long id) {
if (ObjectUtils.isEmpty(id)) {
throw new IllegalArgumentException("用户ID不能为空");
}
return userService.removeById(id);
}
@GetMapping("/list")
@Operation(summary = "查询用户列表", description = "查询所有用户信息")
public List<User> getUserList() {
return userService.list();
}
}
而SpringBoot 2既可以使用SpringFox(Swagger2),也可以使用springdoc-openapi,兼容性更强,但SpringFox已经停止维护,存在很多安全漏洞,即使是SpringBoot 2项目,也推荐迁移到springdoc-openapi。
RFC 7807 ProblemDetails异常规范的原生支持
SpringBoot 3的Spring MVC 6.x原生支持RFC 7807规范,这是一个专门为HTTP API设计的异常响应格式规范,当项目中抛出ResponseStatusException异常时,Spring MVC会自动返回符合规范的ProblemDetails格式的JSON响应,无需开发者手动封装统一返回体。
同时,开发者可以通过全局异常处理器,自定义异常的ProblemDetails响应格式,实现全项目的异常响应统一。完整示例如下:
package com.jam.demo.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
/**
* 全局异常处理器
* @author ken
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ProblemDetail handleIllegalArgumentException(IllegalArgumentException e) {
log.error("参数校验异常", e);
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, e.getMessage());
problemDetail.setTitle("参数校验失败");
problemDetail.setProperty("errorCode", "PARAM_ERROR");
return problemDetail;
}
@ExceptionHandler(RuntimeException.class)
public ProblemDetail handleRuntimeException(RuntimeException e) {
log.error("系统运行时异常", e);
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
problemDetail.setTitle("系统内部错误");
problemDetail.setProperty("errorCode", "SYSTEM_ERROR");
return problemDetail;
}
}
异常响应的格式如下:
{
"type": "about:blank",
"title": "参数校验失败",
"status": 400,
"detail": "用户ID不能为空",
"instance": "/api/user/null",
"errorCode": "PARAM_ERROR"
}
这种标准化的异常响应格式,前后端对接更加规范,第三方系统集成也更加便捷,无需额外的文档说明。而SpringBoot 2没有原生支持该规范,需要开发者手动封装统一返回体,实现全局异常处理,不同项目的实现方式千差万别,规范性很差。
Servlet规范与内置容器的升级
SpringBoot 3支持Servlet 6.0规范,内置的Servlet容器全部基于Jakarta EE 10规范,最低版本要求为Tomcat 10.1.x、Jetty 11.x、Undertow 2.3.x,全面支持HTTP/3协议,完美适配虚拟线程,对IO模型做了深度优化,性能提升明显。而SpringBoot 2支持Servlet 4.0规范,内置Tomcat 9.0.x,仅支持HTTP/2协议,无法适配虚拟线程,很多新特性都不支持。
Servlet 6.0规范移除了大量过时的API,比如SingleThreadModel、HttpSession的废弃方法,同时优化了文件上传的处理逻辑,对大文件上传的内存占用做了优化。SpringBoot 3的文件上传配置属性也做了优化,默认值更加合理,对临时文件的清理机制更加完善,避免了长期运行导致的磁盘空间占用问题。
数据访问与持久层的适配差异
数据访问是后端项目的核心模块,两个版本在事务管理、ORM框架适配、Spring Data系列的支持上,有非常多的关键差异。
事务管理的底层优化
SpringBoot 3的Spring Framework 6.x对事务管理模块做了全面重构,优化了编程式事务的TransactionTemplate,实现了与虚拟线程的深度适配,同时完善了响应式事务的全场景支持,对事务的传播行为、隔离级别做了优化,减少了事务同步的开销,性能提升明显。
SpringBoot 3的@Transactional注解新增了timeoutString属性,支持通过SpEL表达式配置事务超时时间,配置更加灵活,同时移除了大量过时的属性与API,规范了事务的使用方式。而SpringBoot 2的@Transactional注解仅支持固定的超时时间配置,响应式事务的支持非常有限,很多场景下需要额外的适配开发。
日常开发中,推荐使用编程式事务,相比声明式事务,编程式事务的可控性更强,避免了声明式事务因AOP失效导致的事务不生效问题。完整的编程式事务使用示例如下:
package com.jam.demo.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jam.demo.entity.User;
import com.jam.demo.mapper.UserMapper;
import com.jam.demo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
/**
* 用户服务实现类
* @author ken
*/
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Resource
private TransactionTemplate transactionTemplate;
@Override
public Boolean saveUser(User user) {
if (!StringUtils.hasText(user.getUsername())) {
log.error("用户名不能为空");
return Boolean.FALSE;
}
if (ObjectUtils.isEmpty(user.getPassword())) {
log.error("密码不能为空");
return Boolean.FALSE;
}
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
user.setDeleted(0);
return transactionTemplate.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
try {
return save(user);
} catch (Exception e) {
status.setRollbackOnly();
log.error("保存用户信息失败", e);
return Boolean.FALSE;
}
}
});
}
@Override
public Boolean updateUser(User user) {
if (ObjectUtils.isEmpty(user.getId())) {
log.error("用户ID不能为空");
return Boolean.FALSE;
}
user.setUpdateTime(LocalDateTime.now());
return transactionTemplate.execute(status -> {
try {
return updateById(user);
} catch (Exception e) {
status.setRollbackOnly();
log.error("更新用户信息失败", e);
return Boolean.FALSE;
}
});
}
}
ORM框架的适配要求
SpringBoot 3对ORM框架的版本有严格的适配要求,必须使用适配了Jakarta EE规范的版本,否则会出现类找不到、注入失败等问题。核心ORM框架的版本要求如下:
- MyBatis:最低要求3.5.10+,该版本完成了Jakarta EE规范的适配,替换了所有
javax.*包为jakarta.* - MyBatis-Plus:最低要求3.5.3.1+,该版本适配了MyBatis 3.5.10+与Jakarta EE规范,同时兼容SpringBoot 3的核心特性
- Spring Data JPA:最低要求3.0.0+,完全基于Jakarta Persistence 3.1规范,包名从
javax.persistence替换为jakarta.persistence
而SpringBoot 2可以使用MyBatis 3.5.x的所有版本、MyBatis-Plus 3.4.x+的版本,基于javax.*规范,兼容性更强。
完整的MyBatis-Plus使用示例如下,包含MySQL 8.0的建表语句、实体类、Mapper接口、Service层实现:
CREATE TABLE `t_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(64) NOT NULL COMMENT '用户名',
`password` varchar(128) NOT NULL COMMENT '密码',
`phone` varchar(11) DEFAULT NULL COMMENT '手机号',
`email` varchar(64) DEFAULT NULL COMMENT '邮箱',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint NOT NULL DEFAULT '0' COMMENT '逻辑删除标识 0-未删除 1-已删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
CREATE TABLE `t_role` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` varchar(64) NOT NULL COMMENT '角色名称',
`role_code` varchar(64) NOT NULL COMMENT '角色编码',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint NOT NULL DEFAULT '0' COMMENT '逻辑删除标识 0-未删除 1-已删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_role_code` (`role_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色表';
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户实体类
* @author ken
*/
@Data
@TableName("t_user")
@Schema(description = "用户实体")
public class User {
@TableId(type = IdType.AUTO)
@Schema(description = "用户ID", example = "1")
private Long id;
@Schema(description = "用户名", example = "zhangsan")
private String username;
@Schema(description = "密码", example = "123456")
private String password;
@Schema(description = "手机号", example = "13800138000")
private String phone;
@Schema(description = "邮箱", example = "zhangsan@example.com")
private String email;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
@TableLogic
@Schema(description = "逻辑删除标识", example = "0")
private Integer deleted;
}
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户Mapper接口
* @author ken
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
package com.jam.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jam.demo.entity.User;
/**
* 用户服务接口
* @author ken
*/
public interface UserService extends IService<User> {
Boolean saveUser(User user);
Boolean updateUser(User user);
}
Spring Data系列的全面升级
SpringBoot 3的Spring Data模块升级到2023.0+版本,对所有子模块做了全面重构,适配Jakarta EE规范,同时支持了大量数据库的新特性:
- Spring Data Redis 3.0+:升级到Lettuce 6.2+,全面支持Redis 7.0+的新特性,比如Redis Functions、ACL 2.0、集群分片的优化,同时适配虚拟线程,非阻塞性能提升明显
- Spring Data MongoDB 4.0+:支持MongoDB 6.0+的新特性,比如时间序列集合、集群事务的优化
- Spring Data Elasticsearch 5.0+:全面支持Elasticsearch 8.x版本,适配新的客户端API,而SpringBoot 2的Spring Data Elasticsearch最高仅支持Elasticsearch 7.x版本,无法使用8.x的新特性
而SpringBoot 2的Spring Data模块最终版本为2.7.x,已经停止新特性开发,仅做安全维护,无法支持新版本数据库的核心特性。
安全框架的颠覆性变化
Spring Security的升级是SpringBoot 2升级到3的最大痛点之一,SpringBoot 3使用Spring Security 6.0+版本,做了大量的破坏性变更,移除了很多过时的API,完全改变了安全配置的方式。
WebSecurityConfigurerAdapter的完全移除
Spring Security 5.7版本开始废弃WebSecurityConfigurerAdapter,Spring Security 6.0版本完全移除了该类,而SpringBoot 2的绝大多数老项目,都是通过继承WebSecurityConfigurerAdapter来实现安全配置,这也是升级后最常见的配置失效问题。
Spring Security 6.0的标准配置方式,是通过注册SecurityFilterChain的Bean来实现安全配置,所有的安全规则都通过该Bean来定义,配置更加灵活,可读性更强。完整的配置示例如下:
package com.jam.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
/**
* Spring Security安全配置类
* @author ken
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
private static final String[] PERMIT_ALL_URLS = {
"/swagger-ui/**",
"/v3/api-docs/**",
"/actuator/**",
"/api/user/login"
};
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(auth -> auth
.requestMatchers(PERMIT_ALL_URLS).permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(List.of("*"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
而SpringBoot 2的老版本配置方式,是通过继承WebSecurityConfigurerAdapter,重写configure方法来实现,这种方式在SpringBoot 3中完全失效,必须重构为SecurityFilterChain的Bean配置方式。
密码编码与认证机制的升级
Spring Security 6.0默认使用DelegatingPasswordEncoder,默认的密码编码算法为BCrypt,完全移除了MD5、SHA-1等不安全的过时密码编码器,强制开发者使用安全的密码编码算法,提升了系统的安全性。同时,Spring Security 6.0对Argon2、SCrypt等现代密码算法做了深度优化,性能更好,安全性更高。
Spring Security 6.0将OAuth2.0与OIDC 1.0的核心模块完全纳入了主框架,原生支持授权服务器、资源服务器、客户端的全场景配置,无需引入额外的依赖。而SpringBoot 2的OAuth2支持,依赖已经停止维护的spring-security-oauth2项目,需要引入大量的额外依赖,配置繁琐,安全性也无法保障。
CSRF与CORS的配置优化
Spring Security 6.0对CSRF的默认配置做了优化,针对Cookie的SameSite属性做了默认处理,避免了跨站请求伪造的风险,同时针对前后端分离项目,提供了更简单的CSRF令牌配置方式,无需开发者手动处理令牌的传递。而SpringBoot 2的CSRF配置,需要开发者手动处理大量的细节,前后端分离项目的配置非常繁琐。
Spring Security 6.0的CORS配置,可以直接在SecurityFilterChain中定义,无需额外注册CorsFilter,配置更加统一,避免了配置冲突导致的跨域问题。而SpringBoot 2的CORS配置,通常需要单独注册CorsFilter,很容易和Spring MVC的CORS配置产生冲突,导致跨域问题排查困难。
云原生与可观测性的原生能力差异
云原生是SpringBoot 3的核心设计目标,相比SpringBoot 2,SpringBoot 3在可观测性、容器化、Kubernetes适配等方面,做了全面的原生支持,无需引入额外的组件,即可实现云原生应用的全场景能力。
三位一体的可观测性原生支持
SpringBoot 3内置了Micrometer 1.10+版本,全面支持OpenTelemetry规范,实现了Metrics(指标)、Tracing(链路追踪)、Logging(日志)三位一体的可观测性体系,无需引入大量的第三方依赖,即可实现全链路的可观测性。
SpringBoot 3新增的Observation API,实现了一次埋点同时生成指标与链路追踪数据,无需开发者分别埋点,大幅降低了可观测性的接入成本。而SpringBoot 2的Micrometer版本为1.9.x,对OpenTelemetry的支持仅停留在实验阶段,指标与链路追踪需要分别埋点,接入成本高,配置繁琐,不同组件的埋点数据无法打通。
完整的可观测性配置示例如下,对接Prometheus与Zipkin:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
spring:
application:
name: springboot3-demo
management:
endpoints:
web:
exposure:
include: health,info,prometheus,metrics
endpoint:
health:
show-details: always
probes:
enabled: true
metrics:
tags:
application: ${spring.application.name}
tracing:
sampling:
probability: 1.0
zipkin:
tracing:
endpoint: http://127.0.0.1:9411/api/v2/spans
配置完成后,应用会自动生成HTTP请求、数据库操作、Redis操作的指标与链路追踪数据,Prometheus可以通过/actuator/prometheus端点抓取指标数据,Zipkin可以接收链路追踪数据,实现全链路的可观测性。
Actuator端点的全面升级
SpringBoot 3的Actuator端点做了全面升级,新增了/observability端点,用于查看应用的可观测性配置与埋点数据,同时优化了/heapdump、/threaddump端点的性能,支持虚拟线程的线程dump,方便开发者排查虚拟线程的问题。
SpringBoot 3的Actuator原生支持Kubernetes的liveness与readiness健康探针,无需额外配置,即可实现Kubernetes的健康检查,自动感知应用的运行状态,在应用启动失败、出现故障时,自动重启Pod。而SpringBoot 2的Actuator对Kubernetes的健康探针支持有限,需要额外的配置与适配,无法实现原生的集成。
同时,SpringBoot 3的Actuator对端点的安全控制做了优化,可以针对单个端点配置独立的访问权限,控制粒度更细,安全性更高。而SpringBoot 2的Actuator端点权限配置非常繁琐,只能批量配置,无法针对单个端点做精细的权限控制。
容器化与Kubernetes的原生适配
SpringBoot 3的Maven与Gradle插件,原生支持分层镜像的构建,自动将应用的类文件、依赖包分为不同的层,构建Docker镜像时,只有应用代码发生变化时,才会重新构建对应的层,大幅提升了镜像构建的速度与推送效率。而SpringBoot 2的分层镜像支持非常有限,需要手动配置,适配性很差。
SpringBoot 3提供了官方的spring-boot-starter-kubernetes依赖,原生支持Kubernetes的配置中心、服务发现、ConfigMap与Secret的动态刷新,无需引入Spring Cloud Kubernetes等额外的组件,即可实现与Kubernetes生态的深度集成。而SpringBoot 2需要引入Spring Cloud的相关组件,才能实现Kubernetes的适配,配置繁琐,兼容性也无法保障。
SpringBoot 3的云原生架构如下:
升级全流程实战与高频踩坑避坑指南
很多开发者升级失败,都是因为没有遵循正确的升级流程,盲目替换版本号,导致出现大量的兼容性问题。正确的升级流程,应该是分步进行,逐步解决兼容性问题,而不是一步到位。
升级前的必备准备工作
升级前必须完成三项准备工作,否则大概率会出现升级失败的问题:
- 将当前的SpringBoot 2项目升级到2.7.x的最新稳定版本,解决所有的废弃API警告。SpringBoot 3中移除的API,在SpringBoot 2.7.x中都会标记为废弃,提前解决这些问题,可以大幅降低升级后的报错数量。
- 将JDK版本升级到17 LTS,验证项目在JDK 17下可以正常运行。JDK 8到JDK 17有很多破坏性变更,比如模块化系统、反射限制、内部API的移除,提前解决JDK版本的兼容性问题,避免升级SpringBoot后,无法区分是JDK版本的问题还是SpringBoot版本的问题。
- 完成第三方依赖的适配检查,梳理项目中所有的第三方依赖,确认是否有适配Jakarta EE规范的版本。如果有核心依赖没有适配版本,比如自研的内部组件、停止维护的开源组件,需要先完成这些组件的适配,否则无法升级到SpringBoot 3。
分步升级的标准流程
具体的升级步骤如下:
- 升级SpringBoot的父版本到3.x的最新稳定版本,同时修改pom.xml中的java.version为17,确保编译版本正确。
- 使用OpenRewrite的迁移插件,自动完成
javax.*到jakarta.*的包名替换,包括Java代码、配置文件、SPI文件中的所有包名,避免手动替换出现遗漏。OpenRewrite的插件配置如下:
<plugin>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<version>5.33.0</version>
<configuration>
<activeRecipes>
<recipe>org.openrewrite.java.spring.boot3.UpgradeSpringBoot3</recipe>
</activeRecipes>
</configuration>
<dependencies>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-spring</artifactId>
<version>5.33.0</version>
</dependency>
</dependencies>
</plugin>
配置完成后,执行mvn rewrite:run命令,即可自动完成包名替换、废弃API重构、依赖版本升级等操作。 3. 升级所有第三方依赖到适配Jakarta EE规范的最新稳定版本,解决依赖冲突问题,使用mvn dependency:tree命令检查依赖树,排除老版本的javax.*相关依赖。 4. 重构所有移除的API,比如Spring Security的WebSecurityConfigurerAdapter,替换为SecurityFilterChain的配置方式;老版本的RestTemplate推荐替换为RestClient或者声明式HTTP客户端。 5. 执行单元测试与集成测试,解决所有的运行时错误,重点关注接口调用、数据库操作、缓存操作、远程调用等核心场景的兼容性。 6. 开启AOT编译,执行mvn clean compile spring-boot:process-aot命令,验证AOT编译是否正常,解决反射、动态代理、序列化等导致的AOT编译失败问题。 7. 完成全流程的回归测试,验证所有功能正常,升级完成。
高频踩坑点汇总与解决方案
- 包名替换遗漏:手动全局替换包名时,配置文件、SPI文件、自定义注解、MyBatis的Mapper XML文件中的包名容易遗漏,导致项目启动报错。解决方案是使用OpenRewrite的迁移插件自动替换,避免手动操作出现遗漏。
- Swagger2不兼容:升级后Swagger页面无法打开,根源是使用了停止维护的SpringFox(Swagger2),没有适配Jakarta EE规范。解决方案是替换为springdoc-openapi-starter-webmvc-ui,适配OpenAPI 3规范。
- Spring Security配置失效:升级后安全配置完全不生效,根源是使用了已经移除的
WebSecurityConfigurerAdapter配置方式。解决方案是重构为SecurityFilterChain的Bean配置方式,所有安全规则都通过该Bean定义。 - MyBatisPlus注入失败:升级后Mapper接口注入失败,根源是使用了老版本的MyBatisPlus,没有适配Jakarta EE规范。解决方案是升级MyBatisPlus到3.5.3.1+的最新稳定版本,同时升级MyBatis到3.5.10+版本。
- 虚拟线程性能无提升:开启虚拟线程后,并发性能没有提升,根源是在虚拟线程中使用了
synchronized锁或者长时间的阻塞操作,导致虚拟线程无法被JVM卸载。解决方案是使用ReentrantLock替代synchronized锁,避免在虚拟线程中执行长时间的CPU密集型任务。 - AOT编译失败:AOT编译时出现类找不到、反射失败的错误,根源是项目中使用了大量的自定义反射、动态代理、SPI机制,AOT编译时无法生成对应的元数据。解决方案是使用
@RegisterReflectionForBinding注解注册需要反射的类,或者使用GraalVM的tracing agent运行应用,自动生成所需的元数据配置文件。 - JDK模块化冲突:项目启动时出现模块访问错误,根源是JDK 17的模块化系统限制了内部API的访问,老版本的依赖使用了
sun.misc等内部API。解决方案是升级依赖到适配JDK 17的版本,或者在JVM启动参数中添加--add-exports配置,开放对应的模块访问权限。