ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Spring Boot 2不升Boot 3,如何平滑迁到JDK 17?

2026/9/8 16:50:03 拓冰建站 浏览量
Spring Boot 2不升Boot 3,如何平滑迁到JDK 17? 前阵子我接了个“老旧系统JDK基线升级”的任务一个维护了多年的Spring Boot 2.6服务要从JDK 8迁到JDK 17。刚把这个事儿在项目群里一说马上就有两种声音冒出来一种说“都升17了干脆连Spring Boot 3一起升了呗免得以后重复折腾”另一种说“Spring Boot 2升JDK 17肯定处处是坑要不先退回JDK 11凑合吧”。两种方案看起来都有道理但我心里清楚对业务代码量大的老系统来说升Spring Boot 3不是简简单单换个版本号。包里那一堆javax.*import要改成jakarta.*Spring Security 5的配置写法在6里也变了不少加上各种中间件客户端、嵌入式容器兼容性一折腾少说也要一两周还可能把线上稳定状态给打破。至于退回JDK 11就更亏了等于从一个即将过时的LTS搬到另一个也快过时的LTS白折腾一遍。所以我当时的决定是不降JDK也不盲目上Boot 3就让Spring Boot 2这个老伙计在JDK 17上好好跑起来。这套改造方案前前后后花了一个星期从版本梳理、编译链路调整到线上参数配置都试过一轮目前服务已经在JDK 17上稳定运行了两个多月。这篇文章想把这些实操经验完整记录下来给同样处境的人一个能直接抄作业的参考答案。1. 为什么我坚持不降级老项目吃JDK 17红利完全不用动Spring Boot主版本先说结论Spring Boot 2.x和JDK 17从来就不是水火不容的关系。Spring Boot 2.5.6开始官方就已经把Java 17纳入了支持范围后续的2.6.x、2.7.x都对JDK 17做了完整的适配测试。所以“Spring Boot 2必须配JDK 8/11”这个想法是刻板印象JDK 17要求的是Spring Framework 5.3.x以上而Spring Boot 2.6、2.7内部的Spring Framework版本早就达标了。真正让人犹豫的是另一个问题既然要动JDK为什么不同时把Boot主版本也升级了我的判断是这样升级Boot 3意味着全量替换包坐标从javax.servlet到jakarta.servlet从javax.annotation到jakarta.annotation涉及代码改动面非常大。老项目里如果还依赖Spring Security 5的WebSecurityConfigurerAdapter升级Boot 3就等于要重写认证授权这一整块逻辑。风险直接爆表。降回JDK 11不是“退一步海阔天空”而是把问题延后一两年再遇到一次。JDK 11虽然也是LTS但它的免费更新周期早就不如JDK 17宽裕与其再折腾一次不如一步到位。Spring Boot 2.7.x其实是2.x系列的最后一个大版本官方把大量补丁和兼容性修复都集中在这个版本上JDK 17相关的问题相对最少。2.x生命周期虽然已渐入尾声但对于内部系统完全够用这也给了我们留足时间去规划以后的Boot 3迁移而不是被一个JDK升级逼着仓促搬家。所以我最后采用了这个看起来“保守”的路线业务代码不怎么动Spring Boot 2.7.x继续用只把JDK从8切到17。对应地需要重点解决三件事版本基线校准、反射访问限制、隐藏依赖的兼容性问题。2. JDK 17真正的路障不是语法是模块强封装让“反射自由”失效了很多项目从JDK 8切到JDK 17第一反应是“我的代码又没怎么写Java 17的新语法凭什么跑不起来”。这句话对了一半大部分业务代码确实不用改可问题出在那些跑在JVM里的第三方库身上它们在JDK 8时代大量使用反射去访问JDK内部的东西到了JDK 17这扇门被系统管理员锁死了。2.1 JEP 396与强封装从“给警告”到“直接拒绝”JDK 9开始引入模块系统JDK内部API被封装到各个模块里比如java.lang、java.util这些包都属于java.base模块。从JDK 16开始JEP 396把“强封装内部API”从默认关闭变成了默认开启第三方库再想靠反射去访问java.base模块里非导出的包就不再是警告而是直接抛异常。JDK 17正是默认使用这种严格封装的第一个LTS版本。用大白话讲以前你住的小区大门敞开谁都能进来逛逛顶多被保安盯一下现在物业装了门禁没有门禁卡的只能站在门外干瞪眼。那些老库的反射代码就是没有门禁卡的访客。2.2 Spring Boot 2项目里最常见的“全新报错”形态在JDK 8时代你大概率从来没见过这些异常但从JDK 17开始一旦触发就会立刻出现而且异常信息往往写得很清楚反射访问被拒日志长这样java.lang.reflect.InaccessibleObjectException: Unable to make field private static final java.lang.Class[] java.util.Collections$EmptyList.EMPTY_LIST accessible: module java.base does not opens java.util to unnamed moduleCGLIB代理生成时也可能报错java.lang.IllegalAccessError: class org.springframework.cglib.proxy.... cannot access class ...更隐晦的还有这种老字节码库不会读新版class文件直接提示Unsupported class file major version 61。这些报错本质上都指向同一件事某个JAR包或框架代码还在用JDK 8时代的方式做反射、字节码增强、CGLIB代理或Unsafe操作而JDK 17把这条路堵死了。2.3 Spring Boot 2应用里最容易撞墙的代码层根据我的经验JDK 17反射问题不是均匀分布在所有代码里的主要集中在以下几层Spring AOP/CGLIB代理使用Configuration、ConfigurationProperties、EnableAsync等注解时框架可能生成CGLIB子类来增强对象。Spring Framework 5.3.x已经为此做了兼容但如果Boot版本太旧或者引入了旧版CGLIB一样可能撞墙。ORM和JSON库MyBatis、Fastjson、Gson这类库普遍使用反射读写字段对JDK内部类的访问需求比业务代码多得多。连接池和网络库HikariCP、Netty、Jedis等在某些初始化路径上会通过反射访问java.base模块里的类如果版本没有跟上