背景
最近,我们团队在开发一个新服务时,选择了Spring Boot 3 + Dubbo 3 + JDK 17 的技术栈。
项目运行比较顺利,但在Dubbo RPC调用时遇到了一个棘手的问题:其他团队的项目使用的是JDK 11,导致我们的JAR包在他们的环境中运行时出错。
报错信息如下:
从错误提示中可以看出,JAR 包中的类文件版本为 61.0(对应 JDK 17),而目标环境期望的版本是 55.0(对应 JDK 11)。这引发了版本不兼容的问题。
原因分析
Java 源代码(.java 文件)通过 javac 编译成字节码文件(.class 文件)时,会在文件头部写入版本信息。不同 JDK 版本生成的 .class 文件版本不同:
- Java 8:52.0
- Java 11:55.0
- Java 17:61.0
如果要验证我们可以直接使用idea自带的反编译工具直接查看/target/classes下的.class文件。
图片
图片
可以看到不同的JDK版本生成的.class文件头部的版本信息是不一样的,idea反编译可以识别出来。
我们也可以使用javap命令查看.class文件的版本信息。
那么我们就会得到如下字节码信息。
输出中会显示major version: 55,表示该类文件是JDK 11 编译的。
在jdk的源码中verify_class_version方法就会对class的版本进行校验
图片
如果class的版本大于当前的版本就会抛出UnsupportedClassVersionError异常
所以高版本JDK编译的类文件在低版本JDK上运行时,低版本JDK会直接编译报错
解决方案
由于我们是dubbo RPC调用发布简单的RPC接口jar给到其他服务调用,除了基本的接口定义,不会使用高版本JDK的特性,所以解决方式相对来说也比较容易。
如果是一些公用sdk就需要针对性发布不同jdk版本的sdk,这里不做讨论
打包出一个低版本的jar
所以第一个解决方案是我们可以打一个低版本的jar给其他低版本的服务使用
我们项目的模块是如下结构
我们在xiaozou-service中配置了全局的打包插件
指定了source和target的版本为17,所以我们打出来的jar就是JDK 17的版本
但是我们可以在xiaozou-api模块进行覆盖
在xiaozou-api的pom.xml添加如下配置
这样xiaozou-api打出来的jar版本是JDK 11
低版本服务升级JDK
虽然这是一种解决方案,但升级JDK可能涉及较大的工程量和风险,不太现实。
使用 Multi-Release JAR Files
JDK 9引入了Multi-Release JAR Files特性,允许在一个JAR文件中包含多个版本的类文件。
虽然这可以解决版本兼容问题,但配置较为复杂,不适合本场景。
总结
JDK 版本不兼容是Dubbo服务调用中常见的问题,特别是在跨团队协作时。
解决这一问题的最简单方法是打包一个低版本的JAR包,以确保兼容性。
如果不想折腾,保持团队之间JDK版本即可