背景
最近,我们团队在开发一个新服务时,选择了Spring Boot 3 + Dubbo 3 + JDK 17 的技术栈。
项目运行比较顺利,但在Dubbo RPC调用时遇到了一个棘手的问题:其他团队的项目使用的是JDK 11,导致我们的JAR包在他们的环境中运行时出错。
报错信息如下:
java: 无法访问 com.xiaozou.rpc.XiaoZouService
错误的类文件: /Users/xiaozou/Desktop/sofe/java/localRepository/com/xiaozou/1.0.0-SNAPSHOT/xiaozou-1.0.0-SNAPSHOT.jar!/com/xiaozou/rpc/XiaoZouService.class
类文件具有错误的版本 61.0, 应为 55.0
请删除该文件或确保该文件位于正确的类路径子目录中
从错误提示中可以看出,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文件的版本信息。
javap -v XiaoZou.class
那么我们就会得到如下字节码信息。
Last modified 2025年2月26日; size 229 bytes
SHA-256 checksum e8457714c4513babc6686fd96495e7ad7b65658b143023c8b8185bbacb757eea
Compiled from "XiaoZou.java"
public interface com.xiaozou.XiaoZou
minor version: 0
major version: 55
flags: (0x0601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
this_class: #1 // com/xiaozou/XiaoZou
super_class: #3 // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 1
输出中会显示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
- xiaozou-api
- xiaozou-start
我们在xiaozou-service中配置了全局的打包插件
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
指定了source和target的版本为17,所以我们打出来的jar就是JDK 17的版本
但是我们可以在xiaozou-api模块进行覆盖
在xiaozou-api的pom.xml添加如下配置
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
这样xiaozou-api打出来的jar版本是JDK 11
低版本服务升级JDK
虽然这是一种解决方案,但升级JDK可能涉及较大的工程量和风险,不太现实。
使用 Multi-Release JAR Files
JDK 9引入了Multi-Release JAR Files特性,允许在一个JAR文件中包含多个版本的类文件。
虽然这可以解决版本兼容问题,但配置较为复杂,不适合本场景。
总结
JDK 版本不兼容是Dubbo服务调用中常见的问题,特别是在跨团队协作时。
解决这一问题的最简单方法是打包一个低版本的JAR包,以确保兼容性。
如果不想折腾,保持团队之间JDK版本即可