从JDK 8到21:一次Spring Boot 2到3.2的完整迁移与避坑指南
2026/6/30 14:20:32 网站建设 项目流程

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 环境检查清单

在开始升级前,建议先做个完整的系统体检。我通常会创建一个这样的检查表:

  1. 确认当前项目的Maven/Gradle构建配置
  2. 列出所有第三方依赖及其版本
  3. 检查是否有使用废弃的API(比如javax.servlet)
  4. 扫描代码中的反射调用点
  5. 记录当前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的全局替换功能:

  1. 按Ctrl+Shift+R调出替换对话框
  2. 勾选"Regex"选项
  3. 输入javax\.servlet作为查找内容
  4. 输入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.VirtualThreadPool

5.2 虚拟线程的注意事项

虚拟线程不是银弹,有几个关键限制:

  1. synchronized块内发生阻塞会pin住载体线程
  2. JNI调用期间不会释放载体线程
  3. 线程局部变量(ThreadLocal)会有性能损耗

建议添加这些JVM参数来监控问题:

-Djdk.tracePinnedThreads=full -Djdk.virtualThreadScheduler.parallelism=1

6. 测试与验证

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 集成测试策略

建议采用渐进式测试策略:

  1. 先确保所有单元测试通过
  2. 然后测试核心业务流
  3. 最后进行全链路压测

使用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=compact

7.2 监控指标

升级后要特别关注这些指标:

  1. 虚拟线程的创建和销毁速率
  2. 载体线程的利用率
  3. GC停顿时间
  4. 内存使用模式

可以用Prometheus+Grafana搭建监控:

management: endpoints: web: exposure: include: prometheus metrics: tags: application: ${spring.application.name}

8. 回滚方案

即使准备再充分,也要有回滚计划。我建议:

  1. 保留旧版本的Docker镜像
  2. 准备回滚数据库脚本
  3. 记录当前性能基准
  4. 制定分阶段回滚策略

回滚时特别注意数据兼容性问题,特别是如果新版本已经写入了新格式的数据。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询