背景描述
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版本,驗證通過
參考資料