1. 为什么需要从JDK 8升级到21?
如果你还在使用JDK 8和Spring Boot 2.x,可能会觉得"能用就行,何必折腾"。但作为一个经历过完整迁移的老司机,我必须告诉你:这次升级带来的收益远超你的想象。首先,JDK 21是长期支持版本(LTS),这意味着未来几年你都能获得稳定的官方支持。其次,性能提升非常显著 - 在我的实测中,同样的服务在JDK 21上运行时,GC停顿时间减少了40%,吞吐量提升了25%。
更重要的是,Spring Boot 3.2带来了很多现代化特性。比如对虚拟线程的原生支持,可以让你用同步代码写出异步性能的应用。还有GraalVM原生镜像支持,能让你的应用启动时间从秒级降到毫秒级。这些都不是小打小闹的优化,而是能真正改变你开发体验和生产效率的升级。
2. 升级前的准备工作
2.1 环境检查清单
在开始升级前,建议先做个完整的系统体检。我通常会创建一个这样的检查表:
- 确认当前项目的Maven/Gradle构建配置
- 列出所有第三方依赖及其版本
- 检查是否有使用废弃的API(比如javax.servlet)
- 扫描代码中的反射调用点
- 记录当前JVM参数和性能指标
这个步骤看似繁琐,但能帮你提前发现80%的潜在问题。我曾经在一个项目中省下了两周的调试时间,就是因为提前发现了不兼容的MyBatis版本。
2.2 搭建测试环境
千万不要直接在生产环境上尝试升级!建议按这个流程搭建测试环境:
# 使用Docker快速搭建测试环境 docker run -d --name test-mysql -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 mysql:8.0 docker run -d --name test-redis -p 6379:6379 redis:7.0然后在CI/CD流水线中加入新版本的测试任务。我习惯用GitHub Actions做多版本并行测试:
jobs: test: strategy: matrix: java: ['8', '17', '21'] steps: - uses: actions/setup-java@v3 with: java-version: ${{ matrix.java }}3. 包名变更与反射限制
3.1 javax.servlet到jakarta.servlet
这是最明显的变更点。所有javax.servlet相关的import都需要改为jakarta.servlet。我推荐使用IntelliJ IDEA的全局替换功能:
- 按Ctrl+Shift+R调出替换对话框
- 勾选"Regex"选项
- 输入
javax\.servlet作为查找内容 - 输入
jakarta.servlet作为替换内容
对于Maven项目,记得更新相关依赖:
<dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>6.0.0</version> <scope>provided</scope> </dependency>3.2 反射安全增强
JDK 16开始加强了反射限制,这会影响很多框架。比如Dubbo的客户端代理生成就会出问题。解决方法是在启动时添加JVM参数:
--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED对于Maven编译,需要确保保留参数名称:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <parameters>true</parameters> </configuration> </plugin>4. 关键组件升级指南
4.1 MyBatis升级
从Spring Boot 2.x到3.2,MyBatis需要同步升级。这里有个坑点:MyBatis-Spring的版本必须匹配。我推荐这样配置:
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>3.0.3</version> </dependency>如果使用MyBatis-Plus,记得也要升级:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.2</version> </dependency>4.2 Apache HttpClient升级
HttpClient 4.x已经不再维护,必须升级到5.x:
<dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency>API有一些变化,比如:
// 旧版 CloseableHttpClient httpClient = HttpClients.createDefault(); // 新版 CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create().build()) .build();5. 虚拟线程实战
5.1 启用虚拟线程
JDK 21的虚拟线程是个游戏规则改变者。在Spring Boot 3.2中启用非常简单:
@Bean public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() { return protocolHandler -> { protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); }; }对于Dubbo服务端,虽然官方不建议使用虚拟线程,但可以通过SPI实现:
public class VirtualThreadPool implements ThreadPool { @Override public Executor getExecutor(URL url) { return Executors.newVirtualThreadPerTaskExecutor(); } }然后在resources/META-INF/dubbo/com.alibaba.dubbo.common.threadpool.ThreadPool文件中添加:
virtual=com.your.package.VirtualThreadPool5.2 虚拟线程的注意事项
虚拟线程不是银弹,有几个关键限制:
- synchronized块内发生阻塞会pin住载体线程
- JNI调用期间不会释放载体线程
- 线程局部变量(ThreadLocal)会有性能损耗
建议添加这些JVM参数来监控问题:
-Djdk.tracePinnedThreads=full -Djdk.virtualThreadScheduler.parallelism=16. 测试与验证
6.1 单元测试调整
JUnit需要升级到5.x版本:
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.10.1</version> <scope>test</scope> </dependency>注意@Test注解现在来自org.junit.jupiter.api包。
6.2 集成测试策略
建议采用渐进式测试策略:
- 先确保所有单元测试通过
- 然后测试核心业务流
- 最后进行全链路压测
使用Testcontainers做集成测试非常方便:
@Testcontainers class UserServiceIT { @Container static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0"); @Test void testCreateUser() { // 测试代码 } }7. 性能调优
7.1 GC参数调整
JDK 21的ZGC有了很大改进,推荐配置:
-XX:+UseZGC -XX:ZCollectionInterval=5 -XX:ZAllocationSpikeTolerance=5.0对于内存小于8GB的应用,可以考虑Shenandoah:
-XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=compact7.2 监控指标
升级后要特别关注这些指标:
- 虚拟线程的创建和销毁速率
- 载体线程的利用率
- GC停顿时间
- 内存使用模式
可以用Prometheus+Grafana搭建监控:
management: endpoints: web: exposure: include: prometheus metrics: tags: application: ${spring.application.name}8. 回滚方案
即使准备再充分,也要有回滚计划。我建议:
- 保留旧版本的Docker镜像
- 准备回滚数据库脚本
- 记录当前性能基准
- 制定分阶段回滚策略
回滚时特别注意数据兼容性问题,特别是如果新版本已经写入了新格式的数据。