大数跨境
0
0

JDK 21升级总结

JDK 21升级总结 微鲤技术团队
2025-12-09
4


01

背景


将公司的 Java 技术栈从 JDK 11 升级到 JDK 21,不仅仅是一次常规的版本更新,更是一次对生产力、系统性能、安全性和未来技术竞争力的战略投资。JDK 11 作为上一个长期支持版(LTS),稳定可靠,但自其发布以来,Java 平台经历了十个版本的迭代,积累了大量革命性的新特性和底层优化。JDK 21 作为最新的 LTS 版本,是这些创新的集大成者。


升级的必要性
  • 支持终结:Oracle 对 JDK 11 的免费公开更新(Public Updates)已于 2023 年 9 月结束。这意味着,如果不购买商业支持,你的生产环境将无法获得最新的安全补丁和错误修复,这对于任何暴露在网络环境下的应用来说都是一个严重的安全隐患。
  • 框架和库的硬性要求:主流的开源框架和库正在快速拥抱新的 JDK 版本。例如,Spring Framework 6 和 Spring Boot 3.x 已经将最低版本要求提升至 JDK 17。这意味着,如果你的团队想使这些框架的最新功能、性能优化和安全修复,升级 JDK 是一个无法绕过的前提条件



02

JDK21版本特性


虚拟线程

虚拟线程 (Virtual Threads - Project Loom):这是 JDK 21 的王牌特性**。它从根本上改变了 Java 的并发编程模型。

  • 之前 (JDK 11):我们依赖昂贵的平台线程(与操作系统线程 1:1 对应),通过复杂的异步编程(如 CompletableFuture)和线程池来处理高并发,代码复杂且难以调试。
  • 在 (JDK 21):我们可以用极其轻量级的虚拟线程,同步、顺序的编码风格写出高并发程序。一个 JVM 可以轻松创建数百万个虚拟线程,使 I/O 密集型应用的吞吐量得到数量级的提升,同时显著降低了代码的复杂性

新一代垃圾收集器 (GC)

  • ZGC 和 Shenandoah 的成熟这两款低延迟 GC 在 JDK 21 中已成为生产可用级别,能够将 GC 暂停时间控制在亚毫秒级别,对于需要稳定低延迟的实时应用(如交易系统、实时推荐)至关重要。

  • G1 GC 的持续改进作为默认 GC,G1 在新版本中也获得了大量优化,吞吐量和延迟表现比 JDK 11 中更好。

  • 新版本引入了大量语法糖和新特性,旨在消除冗长的“样板代码”,让代码更简洁、更安全、更具表达力。


语法糖和新特性

新版本引入了大量语法糖和新特性,旨在消除冗长的“样板代码”,让代码更简洁、更安全、更具表达力。

  • Records (记录类 - JDK 16)一句话定义不可变的数据载体类 (DTO/POJO),自动生成构造函数、equals()、hashCode()、toString() 和 getter。

    JDK 11

    public final class Point {
      private final int x;
      private final int y;
      // + 构造函数, getters, equals, hashCode, toString... (约50行代码)
    }

    JDK 21

    public record Point(int x, int y) { } // 1行代码搞定
  • Switch 模式匹配 (Pattern Matching for Switch - JDK 21)让 switch 语句变得前所未有的强大和安全,可以直接对对象的类型和属性进行判断,消除了繁琐的 if-else 和类型强转。

    JDK 11

    Object obj = ...;
    if (obj instanceof String) {
      String s = (String) obj;
      System.out.println("String: " + s.toUpperCase());
    } else if (obj instanceof Integer) {
      // ...
    }

    JDK 21

    Object obj = ...;
    switch (obj) {
      case String s -> System.out.println("String: " + s.toUpperCase());
      case Integer i -> System.out.println("Integer: " + i);
      default -> { }
    }
  • 文本块 (Text Blocks - JDK 15)优雅地编写多行字符串,告别丑陋的 + 拼接和 \n 转义,尤其适合编写 SQL、JSON、HTML 等。

JDK 11

String json = "{\n" + " \"name\": \"John\",\n" + " \"age\": 30\n" + "}";

JDK 21:

String json = """
  {
    "name": "John",
    "age": 30
  }
  """;


其他重要特性

  • Record 模式 (Record Patterns, JDK 21):优雅地解构 Record 对象。
  • Sealed Classes (密封类, JDK 17):更精确地控制类的继承关系,构建更严谨的领域模型。
  • var 关键字的改进:让局部变量类型推断更强大。
  • 更友好的 NullPointerExceptions:NPE 异常信息会明确指出哪个变量是 null。



03

Spring Boot版本选择


SpringBoot和JDK版本兼容

SpringBoot Version
JDK Version
来源
2.1
8 - 12
https://docs.spring.io/spring-boot/docs/2.1.x/reference/html/getting-started-system-requirements.html
2.2 - 2.3
8 - 15
https://docs.spring.io/spring-boot/docs/2.1.x/reference/html/getting-started-system-requirements.html
2.4
8 - 16
https://docs.spring.io/spring-boot/docs/2.4.x/reference/html/getting-started.html#getting-started-system-requirements
2.5
8 - 18
https://docs.spring.io/spring-boot/docs/2.5.x/reference/html/getting-started.html#getting-started.system-requirements
2.6
8 - 19
https://docs.spring.io/spring-boot/docs/2.6.x/reference/html/getting-started.html#getting-started.system-requirements
2.7
8 - 21
https://docs.spring.io/spring-boot/docs/2.7.x/reference/html/getting-started.html#getting-started.system-requirements
3.0
17 - 21
https://docs.spring.io/spring-boot/docs/3.0.x/reference/html/getting-started.html#getting-started.system-requirements
3.1
17 - 21
https://docs.spring.io/spring-boot/docs/3.1.x/reference/html/getting-started.html#getting-started.system-requirements
3.2
17 - 23
https://docs.spring.io/spring-boot/docs/3.2.x/reference/html/getting-started.html#getting-started.system-requirements

虽然Spring Boot 3.x 版本带来了许多激动人心的新特性,但其颠覆性的 javax. 到 jakarta. 命名空间迁移为项目带来了不可忽视的巨大挑战。从 Spring Boot 2.x 升级到 3.x 不仅仅是一次常规的版本升级,而是一次伤筋动骨的底层 API 迁移


什么是命名空间变更?

由于 Java EE 规范的所有权从 Oracle 转移到了 Eclipse 基金会,其名称也变更为 Jakarta EE。这导致了所有相关 API 的 Java 包名从 javax. 强制变更为 jakarta.。例如:

  • javax.servlet.http.HttpServletRequest -> jakarta.servlet.http.HttpServletRequest
  • javax.persistence.Entity -> jakarta.persistence.Entity
  • javax.validation.constraints.NotNull -> jakarta.validation.constraints.NotNull


迁移成本巨大?

  1. 全局代码修改:这不仅仅是简单的“查找和替换”。项目中所有涉及到 Servlet API、JPA、Bean Validation 等规范的 import 语句都需要修改。对于一个成熟的大型项目,这涉及成百上千个文件的改动。
  2. 整个依赖生态系统的颠覆:这是最棘手的问题。不仅仅是我们的代码,我们所依赖的所有第三方库(如数据库驱动、消息队列户端、缓存工具、安全框架、自定义 Starters 等)都必须提供与 Jakarta EE 兼容的新版本。
  3. 传递性依赖冲突:即使我们升级了直接依赖,这些依赖的传递性依赖(它们依赖的库)可能仍然停留在 javax 命名空间,这将导致灾难性的类路径冲突(ClassNotFoundException, NoClassDefFoundError),解决这些冲突非常耗时且痛苦。
  4. 潜在的“深水区” Bug:某些库可能声称兼容 Jakarta EE,但在边缘场景下存在未被发现的 Bug。这种因底层 API 变更引入的问题通常难以定位和修复。


决策结论

选择 Spring Boot 2.7.18 意味着我们可以完全避免这个高风险、高成本的迁移过程,将团队的宝贵时间和精力聚焦于业务功能的开发和交付上。



04

依赖库版本


依赖库版本可通过如下链接进行获取:

https://docs.spring.io/spring-boot/docs/2.7.18/reference/htmlsingle/#appendix.dependency-versions

https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-parent/2.7.18

依赖
版本
org.springframework
5.3.31
spring-data-redis
2.7.18
spring-data-mongodb
3.4.18
commons-lang3
3.12.0
commons-collections
3.2.2
commons-collections4
4.4
jstl
1.2
guava
32.1.3-jre
jackson-mapper-asl
1.9.8
jackson-core
2.16.1
hibernate-validator
6.2.5.Final
javax.el
3.0.0
javax.validation
2.0.1.Final
javax.xml.bind
2.3.1
suishen.com.baidu.disconf
2.6.38-SNAPSHOT
org.reflections
0.9.11
lombok
1.18.30
org.jetbrains
24.0.1
suishen-libs
3.0.0-jdk21-SNAPSHOT
suishen-redis
3.0.0-jdk21-SNAPSHOT
suishen-webx-parent
3.0-jdk21-SNAPSHOT
suishen-webx-core
3.0-jdk21-SNAPSHOT
suishen-root-pom
3.0-jdk21-SNAPSHOT



05
升级步骤


开发工具准备

  • idea升级到2025.2版本
  • maven使用3.6.1版本
  • tomcat 8或9

步骤1、pom文件修改

(1)升级suishen-webx-parent版本

    <parent>
        <groupId>suishen-webx</groupId>
        <artifactId>suishen-webx-parent</artifactId>
        <version>3.0-jdk21-SNAPSHOT</version>
    </parent>

(2)完成后确认项目中的其他依赖库版本:


步骤2、更改applicationContext-mongodb.xml配置

(1)原配置:spring-data-mongodb 1.10.15.RELEASE

!-- 定义mongo对象,对应的是mongodb官方jar包中的Mongo,replica-set设置集群副本的ip地址和端口,多个以英文逗号分割 -->
    <mongo:mongo-client id="mongoClient" replica-set="${mongo.hostport}" credentials="${mongo.username}:${mongo.password}@${mongo.dbname}">
        <mongo:client-options
                connections-per-host="${mongo.connectionsPerHost}"
                threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
                connect-timeout="${mongo.connectTimeout}"
                max-wait-time="${mongo.maxWaitTime}"
                socket-keep-alive="${mongo.socketKeepAlive}"
                socket-timeout="${mongo.socketTimeout}"/>
    </mongo:mongo-client>

    <mongo:db-factory dbname="${mongo.dbname}" mongo-ref="mongoClient"/>

(2)更改后配置:spring-data-mongodb 3.4.18

 <!-- 构建连接字符串,使用 SpEL 表达式对用户名和密码进行 URL 编码,避免特殊字符问题 -->
    <bean id="mongoConnectionString" class="com.mongodb.ConnectionString">
        <constructor-arg value="#{'mongodb://' + T(java.net.URLEncoder).encode('${mongo.username}', 'UTF-8') + ':' + T(java.net.URLEncoder).encode('${mongo.password}', 'UTF-8') + '@${mongo.hostport}/${mongo.dbname}?authSource=${mongo.dbname}&maxPoolSize=${mongo.connectionsPerHost:500}&connectTimeoutMS=${mongo.connectTimeout:5000}&socketTimeoutMS=${mongo.socketTimeout:10000}&serverSelectionTimeoutMS=${mongo.maxWaitTime:5000}'}"/>
    </bean>

    <!-- 创建 MongoClient -->
    <bean id="mongoClient" class="com.mongodb.client.MongoClients" factory-method="create">
        <constructor-arg ref="mongoConnectionString"/>
    </bean>

    <!-- 创建 MongoDatabaseFactory -->
    <bean id="mongoDbFactory" class="org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory">
        <constructor-arg ref="mongoClient"/>
        <constructor-arg value="${mongo.dbname}"/>
    </bean>


步骤3、修改相关代码

mongo排序

修改前:new Sort(Sort.Direction.DESC, "startTime")
修改后:Sort.by(Sort.Direction.DESC, "startTime")

reids指令

修改前:ZParams zParams = new ZParams().aggregate(ZParams.Aggregate.SUM).weightsByDouble(weightsDouble)
修改后:ZParams zParams = new ZParams().aggregate(ZParams.Aggregate.SUM).weights(weightsDouble)


步骤4、eone流水线配置更改

基础环境:tomcat9_jdk21

步骤5、发布观察日志

发布后需要重点关注以下方面:    

  • 应用启动日志:检查是否有类加载错误、依赖冲突或配置问题
  • 性能指标:监控 CPU、内存使用情况,观察 GC 日志,确认 ZGC/G1 运行正常
  • 功能验证:全面回归测试核心业务功能,确保升级后功能正常
  • 错误日志:密切关注应用错误日志,特别关注与 JDK 版本相关的异常
  • 第三方服务调用:验证与外部服务(如数据库、Redis、消息队列等)的连接和交互是否正常

06
总结


本次 JDK 21 升级是一次重要的技术迭代,不仅解决了 JDK 11 安全支持终止的问题,更为团队带来了:

  • 长期技术支持:JDK 21 作为最新的 LTS 版本,将获得至少 8 年的安全更新支持
  • 性能提升:虚拟线程、新一代 GC 等特性将显著提升应用的并发处理能力和响应速度
  • 代码质量:Records、模式匹配等现代语法特性让代码更简洁、更安全、更易维护
  • 技术竞争力:为未来采用 Spring Boot 3.x 等新技术栈奠定基础

升级过程中虽然需要处理依赖版本调整和部分代码适配,但通过选择 Spring Boot 2.7.18,避免了 Jakarta EE 命名空间迁移的巨大成本,在获得 JDK 21 核心优势的同时,保持了升级路径的平稳可控。

建议在升级完成后,逐步探索和应用 JDK 21 的新特性(如虚拟线程),充分发挥新版本的技术优势,持续提升系统的性能和开发效率。



【声明】内容源于网络
0
0
微鲤技术团队
践行数据驱动理念,相信技术改变世界。
内容 25
粉丝 0
微鲤技术团队 践行数据驱动理念,相信技术改变世界。
总阅读53
粉丝0
内容25