背景描述
15年的一个老系统,使用的ssh框架,在1.6jdk下运行了五年,最近因部署环境切换,新环境对JDK版本统一升级为1.8版本,在1.8版本jdk下,系统启动报错。
(整个问题排查过程由新环境平台厂商大牛整理,并征求同意后发出来。)
问题概述
先说结论和解决方案:
-
问题描述:spring3.* 版本不支持JDK8,启动时报错。
-
问题原因:spring使用asm类库操作Java class文件,spring3.* 依赖的asm类库版本比较老,而JDK8之后Java class格式有变化,老版本的asm类库不能支持导致报错。
-
解决方式和建议:
-
升级spring到spring4以上版本(新应用或老应用大改推荐)
-
spring4开始正式支持JDK8,实际是升级了配套的asm类库到可以支持JDK8的版本
-
适用于新应用(新应用也强烈不建议使用spring3版本)
-
对于老应用从spring3升级到spring4/5可能会引发很多兼容性的问题,最好是整个spring生态一起升级到新版本,但这样工作量会比较大,因此只适合老应用大改。
-
-
升级spring到spring3.2.18版本(老应用小改推荐):
-
Spring3.2.18版本是spring3系列最后一个版本
-
保持了spring3的兼容性,同时asm类库也升级到了可以支持JDK8的版本
-
可以比较好的平衡:改动量小,能解决spring3和jdk8的冲突问题,又不必让老应用面临升级spring4大版本的风险
-
-
降级JDK到7(老应用完全改不了的最后方案)
-
不建议采用,JDK7过于陈旧,而且会导致后续没法使用JDK8的特性和支持这些特性的类库
-
偏离主流技术栈(统一使用JDK8),增加开发运维的复杂度
-
建议:只有在上面两个方法都无法使用时,不得已而为之的最后备选方案
-
-
问题排查
如果对这个问题的细节有兴趣,请细看下面的内容。
问题原始错误信息和输入
问题分析
从异常信息看,是spring在启动初始化时,通过 spring-asm 类库操作Java class文件时报错:
Caused by: java.lang.IllegalArgumentException at org.springframework.asm.ClassReader.<init>(Unknown Source) at org.springframework.asm.ClassReader.<init>(Unknown Source) at org.springframework.asm.ClassReader.<init>(Unknown Source) at org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:52) at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:80) at org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:101) at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:257) ... 47 more
Spring官方对这个问题的分析:
社区2013年就报告了这个版本冲突问题,从分析上看是spring使用的 asm 类库版本太低,而JDK8之后Java class格式有变化,老版本的asm类库不能支持处理 Jdk8 的class文件导致报错抛异常。
ASM类库介绍
特别高亮一下 ASM 类库,ASM 是Java社区广泛使用的Java字节码操作和分析框架工具,可以用来修改已有的java class文件或者动态生成java class。
-
ASM官方网站:https://asm.ow2.io/
-
ASM的版本列表:https://asm.ow2.io/versions.html
ASM对java 8的支持,始于 2013年10月发布的 asm 5.0 bata 版本:
因此,要解决和JDK8的版本冲突问题,就必须升级spring配套的asm类库到asm5.0版本。
Spring使用ASM类库的方式
Spring重度依赖ASM,但spring使用ASM类库的方式比较特殊,历史上有三次变更:
-
直接使用官方ASM(spring1和spring2):
这是普通使用jar包依赖的方式,在spring 的maven依赖中引入asm,以spring 2 最后一个版本 spring 2.5.6为例:
<dependency> <groupId>asm</groupId> <artifactId>asm</artifactId> <version>2.2.3</version> <optional>true</optional> </dependency> <dependency> <groupId>asm</groupId> <artifactId>asm-commons</artifactId> <version>2.2.3</version> <optional>true</optional> </dependency> <dependency> <groupId>asm</groupId> <artifactId>asm-util</artifactId> <version>2.2.3</version> <optional>true</optional> </dependency>
注意 asm.jar 中的 java package 名是
org.objectweb.asm
: -
spring-asm(spring3.0和spring3.1版本)
Spring3之后没有再按照普通的方式使用asm官方类库,而是做了一次 repackge,将asm的java package 从
org.objectweb.asm
改名为org.springframework.asm
,然后打包并发布为单独的 spring-asm 类库如 spring-asm-3.0.5.RELEASE.jar对比如下图:
在这里可以看到 spring-asm 类库的历史版本记录,从3.0.0版本开始,到3.1.4版本结束:
https://repo.spring.io/release/org/springframework/spring-asm/
-
asm in spring-core(spring3.2及之后的版本)
在spring3.2版本之后,spring修改了repackage asm的方式,package名维持不变,但是不再使用 spring-asm 这样的单独类库,而是把 asm 的内容打包到了 spring-core 中:
spring3.2系列早期版本repackage 的是 asm 4.0 版本,依然不支持jdk8;但在后期版本(应该是从3.2.14或者3.2.16)开始就repackage了支持 asm 5.0版本。
从 spring 的 java docs 文档中可以看到:
https://docs.spring.io/spring/docs/3.2.x/javadoc-api/org/springframework/asm/package-summary.html
Spring's repackaging of
简单和安全起见,升级到3.2最后一个版本 3.2.18 是可以确认支持jdk8。
问题验证
出现问题的应用,使用的spring版本是 spring 3.0.5 ,配套的spring-asm 3.0.5 对应的asm是3.0版本,不支持jdk8,因此报错。
验证了以下几种解决方案:
-
降级JDK到7版本,验证通过:使用了Orcale JDK7U80版本,这是JDK7最后一个小版本
-
升级spring到3.2.18版本,验证通过