记录JDK 1.8 升到21的过程

把一头大象塞进冰箱分为几步,首先把冰箱门打开,然后把大象放进去,最后把冰箱门带走。

最后的步骤很有意思,如果不是把冰箱门带走而是把冰箱门关上,细想一下似乎更好笑,但没有带走的笑点来的那么直接。

在这里,我其实开始犯了无限发散的毛病,想分析一下为什么把一件听上去不可思议的事情描述的这么简单明了会让人觉得好笑,但今日的重点显然不在这里,我必须快点打住,切入整体,才能赶紧写完,赶紧下班。

把一个历史悠久,依赖众多,代码冗长的工程从JDK从1.8升级到21,在我看起来其实就像是要把大象放进冰箱里。

如果这件事情完全让我自由发挥,我会给这个事情定一个计划:

  1. 先调研确定难点和耗时,确定这件事情的投入产出比和投产计划
  2. 开始升级,一边升级一边写过程文档
  3. 回归测试,确保生产不能出问题
  4. 把过程文档整理成技术文档,确保后面如果还干类似的活,能有东西可以看

但今天干这个活的节奏是:你先把它升上去,不要block功能的开发,后面我们会按照完整的流程把这个事情做完。

ok。。。那么上面计划中的1234,我还能保住2和4。

开始升级前的准备

开始升级之前还是有一些事情要做的:

  1. 准备好本地的JDK21环境,Mac要注意arm JDK的选择,配置好环境变量
  2. 确认目标JDK版本对应的Spring版本及其他重要二方库依赖的版本
  3. 如果时间充足,还是可以看一些别人的踩坑文档

在时间2上花了一些时间,让AI给我列了一个清单,又从清单的链接跳转到官网自己看了一下,比较快速的确认了版本问题;

JDK和踩坑的文章都是很久之前就有了解的,这个临时抱佛脚其实作用也有限。

接下来就是开始干活了。

升级过程中的重要问题解决

升级过程中遇到了挺多依赖不兼容和JDK底层方法不兼容问题的,有些很简单跟到pom直接就fix了,有些总也抓不到不兼容的罪魁祸首,有些需要改代码,不能记下来全部了。

写在这里的符合两个条件:

  • 记得住
  • 不是公司二方库导致的特有的问题

javax -> jakarta;

这个变更是由于维护团队变更引起的,导致包名变化。最常见的受影响的包括:

  • servlet.http
  • annotation

这个会直接导致依赖更新后代码大规模的报错,之前的HttpServletRequest、HttpServletResponse、annotation包里的注解全部需要手动修改import

改完只能说代码不报错了,接下来是第二部分的影响,和一些老旧三方库中的Javax包路径下的代码不兼容,表现为:

  • 自定义实现开始报错,class不匹配
  • 启动报错,无法找到Javax下的对象

需要报一个错误手动升级一个,有些甚至可能无法升级,需要手动写适配器。

Spring高版本默认禁止循环依赖

改代码是最好的,不改的话,打开开关。

1
spring.main.allow-circular-references=true

org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter丢失

找到出错的起始位置,把对应的依赖包升级到Spring framework 6.x的版本。

这个可能有很多个,保持耐心。

Mybatis 依赖冲突

报错很直接,升级改代码完事。

Prometheus的jakarta依赖问题

使用Jakarta版本的Prometheus依赖。

1
2
3
4
5
<dependency>
    <groupId>io.prometheus</groupId>
    <artifactId>simpleclient_servlet_jakarta</artifactId>
    <version>0.16.0</version> <!-- 或更高版本 -->
</dependency>

Invalid value type for attribute ‘factoryBeanObjectType’: java.lang.String

如果自定义的FactoryBean,那就改代码。

如果是第三方的,那就升级。

JDK限制变化

报错信息: Unable to make field static final boolean com.sun.jndi.rmi.registry.RegistryContext.trustURLCodebase accessible: module jdk.naming.rmi does not “opens com.sun.jndi.rmi.registry” to unnamed module

这个应该是升级之后对于JDK8的配置不生效了,新的应该这么配置:

1
--add-opens jdk.naming.rmi/com.sun.jndi.rmi.registry=ALL-UNNAMED

这个类型的问题可能不止一个,哪个报错开哪里。

再来一个报错信息: Unable to make field private static final int sun.net.ResourceManager.maxSockets accessible: module java.base does not “opens sun.net” to unnamed module

1
--add-opens java.base/sun.net=ALL-UNNAMED

升级之后理论上应该干啥

标准答案当然是完整的回归。

实际上完整的回归对基建的要求很高,大部分的工程很难完成。

这个时候就会依赖一些工程上的方法:

  • 功能拆分,核心链路一定要回归,非核心链路选择性回归
  • CICD,如果有自动化测试,能跑多少跑多少
  • 灰度,先选一部分客户试一下

其他的,烧香不知道有用没有。

好了,结束。

明天开始往JDK21的冰山工程里写代码了。