1. 程式人生 > 實用技巧 >spring3.*和jdk8版本衝突問題及解決方案

spring3.*和jdk8版本衝突問題及解決方案

背景描述

15年的一個老系統,使用的ssh框架,在1.6jdk下運行了五年,最近因部署環境切換,新環境對JDK版本統一升級為1.8版本,在1.8版本jdk下,系統啟動報錯。

(整個問題排查過程由新環境平臺廠商大牛整理,並徵求同意後發出來。)

問題概述

先說結論和解決方案:

  • 問題描述:spring3.* 版本不支援JDK8,啟動時報錯。

  • 問題原因:spring使用asm類庫操作Java class檔案,spring3.* 依賴的asm類庫版本比較老,而JDK8之後Java class格式有變化,老版本的asm類庫不能支援導致報錯。

  • 解決方式和建議:

    1. 升級spring到spring4以上版本(新應用或老應用大改推薦)

      • spring4開始正式支援JDK8,實際是升級了配套的asm類庫到可以支援JDK8的版本

      • 適用於新應用(新應用也強烈不建議使用spring3版本)

      • 對於老應用從spring3升級到spring4/5可能會引發很多相容性的問題,最好是整個spring生態一起升級到新版本,但這樣工作量會比較大,因此只適合老應用大改。

    2. 升級spring到spring3.2.18版本(老應用小改推薦):

      • Spring3.2.18版本是spring3系列最後一個版本

      • 保持了spring3的相容性,同時asm類庫也升級到了可以支援JDK8的版本

      • 可以比較好的平衡:改動量小,能解決spring3和jdk8的衝突問題,又不必讓老應用面臨升級spring4大版本的風險

    3. 降級JDK到7(老應用完全改不了的最後方案)

      • 不建議採用,JDK7過於陳舊,而且會導致後續沒法使用JDK8的特性和支援這些特性的類庫

      • 偏離主流技術棧(統一使用JDK8),增加開發運維的複雜度

      • 建議:只有在上面兩個方法都無法使用時,不得已而為之的最後備選方案

問題排查

如果對這個問題的細節有興趣,請細看下面的內容。

問題原始錯誤資訊和輸入

JDK 8和spring 3.x不匹配,應用在啟動時初始化spring時報錯,實際異常如下:

問題分析

從異常資訊看,是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官方對這個問題的分析:

IllegalArgumentException initializing an application compiled with Java 8:

社群2013年就報告了這個版本衝突問題,從分析上看是spring使用的 asm 類庫版本太低,而JDK8之後Java class格式有變化,老版本的asm類庫不能支援處理 Jdk8 的class檔案導致報錯拋異常。

ASM類庫介紹

特別高亮一下 ASM 類庫,ASM 是Java社群廣泛使用的Java位元組碼操作和分析框架工具,可以用來修改已有的java class檔案或者動態生成java class。

ASM對java 8的支援,始於 2013年10月釋出的 asm 5.0 bata 版本:

因此,要解決和JDK8的版本衝突問題,就必須升級spring配套的asm類庫到asm5.0版本。

Spring使用ASM類庫的方式

Spring重度依賴ASM,但spring使用ASM類庫的方式比較特殊,歷史上有三次變更:

  1. 直接使用官方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

  2. 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/

  3. 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 org.objectweb.asm 5.0 (for internal use only).

    簡單和安全起見,升級到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版本,驗證通過

參考資料