![Mybatis-Plus 版本沖突觸發“Could not convert argument value of type [java.lang.String] to required type [java.lang.Class]”的 java.lang.NoClassDefFoundError 例外](https://img.uj5u.com/2021/12/30/293351301156021.png)
你好,我是看山,
今天專案依賴了一個基礎組件之后,啟動失敗,排查程序走了一些彎路,最終確認是因為依賴組件版本沖突造成了java.lang.NoClassDefFoundError例外,下面是排查程序,希望可以給你提供一些思路,
觀察例外堆疊
下面是列印的例外堆疊資訊,從其中提煉可能的關鍵資訊,能夠找到“Could not convert argument value of type [java.lang.String] to required type [java.lang.Class]”,還有“Unresolvable class definition for class [cn.howardliu.demo.AddressMapper]”,繼續從例外堆疊中找一下發生的時機,可以發現是呼叫AbstractAutowireCapableBeanFactory.createBeanInstance時,這個方法是創建 Bean 實體,
這塊是例外資訊(getMessage 的內容,橫向太長,手動換行了):
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'methodValidationPostProcessor' defined in class path resource [org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.class]:
Unsatisfied dependency expressed through method 'methodValidationPostProcessor' parameter 0;
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'addressMapper' defined in file [/Users/liuxinghao/Documents/work/code/cn.howardliu/effective-spring/target/classes/cn/howardliu/demo/AddressMapper.class]:
Unsatisfied dependency expressed through constructor parameter 0:
Could not convert argument value of type [java.lang.String] to required type [java.lang.Class]:
Failed to convert value of type 'java.lang.String' to required type 'java.lang.Class';
nested exception is java.lang.IllegalArgumentException:
Unresolvable class definition for class [cn.howardliu.demo.AddressMapper]
下面是例外堆疊:
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:799) ~[spring-beans-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:540) ~[spring-beans-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1341) ~[spring-beans-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1181) ~[spring-beans-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:556) ~[spring-beans-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516) ~[spring-beans-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324) ~[spring-beans-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322) ~[spring-beans-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207) ~[spring-beans-5.2.13.RELEASE.jar:5.2.13.RELEASE]
其他例外堆疊資訊可以忽略了
我們可以根據目前有效的資訊進行排查,首先看下我們的cn.howardliu.demo.AddressMapper定義是否有問題,再看看依賴它的 Service 有沒有問題,什么問題也沒有發現,下一個檢查點是配置,比如@MapperScan是否正確、Mapper 類上有沒有加上@Mapper注解,發現也沒有問題,
從例外資訊找不到思路了,只能從代碼入手了,
這里需要說一下,列印例外資訊至關重要,直接影響我們排錯的思路,如果列印的資訊沒有辦法準確定位,我們將會花費大量的時間查找真正的錯誤,這就需要走查代碼,有時候還需要一些經驗,
定位問題
我們由例外堆疊``ConstructorResolver.createArgumentArray(ConstructorResolver.java:799) 入手,跟著斷點往下追,最侄訓追到org.springframework.util.ClassUtils#forName方法,其中會拋出例外的代碼是下面這塊:
try {
return Class.forName(name, false, clToUse);
}
catch (ClassNotFoundException ex) {
int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
if (lastDotIndex != -1) {
String innerClassName =
name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
try {
return Class.forName(innerClassName, false, clToUse);
}
catch (ClassNotFoundException ex2) {
// Swallow - let original exception get through
}
}
throw ex;
}
出現錯誤的是Class.forName(name, false, clToUse)這句,name傳的是"cn.howardliu.demo.AddressMapper"字串,拋出的例外是java.lang.NoClassDefFoundError,由于不是ClassNotFoundException例外,不會進入catch邏輯,會直接向上拋出,
找到錯誤我們就好定位問題了,
一般來說,java.lang.NoClassDefFoundError錯誤是需要加載的類能夠找到,但是加載時出現了例外,簡單說就是,類的定義有問題,我們借助 JD-GUI 反編譯一下運行 jar 包,結果如下:
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import cn.howardliu.demo.Address;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AddressMapper extends BaseMapper<Address> {}
觀察仔細的話,我們可以看到import com.baomidou.mybatisplus.core.mapper.BaseMapper;這行沒有下劃線,也就是說,在反編譯工具中追溯不到這個介面,推斷出來就是在運行環境中,找不到BaseMapper這個類定義,
所以,當Class.forName加載類的時候拋出了java.lang.NoClassDefFoundError例外,
解決問題
如果有一定經驗,就會立刻想到,大概率出現了依賴 jar 的版本沖突,
我們可以借助 maven 命令列找到版本沖突的依賴:
mvn dependency:tree -Dverbose | grep conflict
列印結果為:
[INFO] | +- (com.baomidou:mybatis-plus:jar:3.1.2:compile - omitted for conflict with 2.1.6)
我們也可以借助 IDEA 的可視化工具,在 pom.xml 上打開依賴圖:

我們可以看到 mybatis-plus 的紅線指示出沖突資訊:

結論就是 Mybatis-Plus 版本沖突了,專案中依賴了 mybatis-plus 的 2.1.6 和 3.1.2 兩個版本,由于 2.1.6 路徑更短,最終被選中,
此時只需要將低版本的依賴去掉即可,
復盤問題
mybatis-plus 的版本問題
為什么低版本的 mybatis-plus 會造成類加載失敗呢?是因為 mybatis-plus 跨版本更新時,把BaseMapper的包路徑改了:
// 3.1.2 版本
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
// 2.1.6 版本
import com.baomidou.mybatisplus.mapper.BaseMapper;
而且還不止這一個,IService、ServiceImpl、TableName、TableField、Model、TableField等等,很多常用的類都改了位置,所以會造成找不到依賴的類,編譯是 3.1.2 依賴還在運行環境中,就會出現編譯沒有問題,執行時出現加載類例外,
想要工程化的解決這個問題,我們可以創建基礎的依賴 bom 配置,定義好基礎依賴包,在專案中不在指定版本,這樣做到統一版本,可以有效的避免這類問題,
我們還可以在 CI/CD 中加入沖突依賴檢查,如果發現沖突依賴,就終止流水線,
真實體外被隱藏問題
接下來我們看下為什么明明是java.lang.NoClassDefFoundError例外,結果例外堆疊中列印的是一堆不相干的錯誤,繼續跟著剛才的斷點 Debug:
org.springframework.util.ClassUtils#resolveClassName會捕捉LinkageError錯誤,然后包裝成IllegalArgumentException例外,這個時候真是例外還是繼續上拋,
然后在org.springframework.beans.TypeConverterSupport#convertIfNecessary方法會包裝成TypeMismatchException例外,此時,真實體外還在例外cause引數中,并沒有丟失,
等回到org.springframework.beans.factory.support.ConstructorResolver#createArgumentArray方法后,捕捉例外的方法是:
try {
convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam);
}
catch (TypeMismatchException ex) {
throw new UnsatisfiedDependencyException(
mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
"Could not convert argument value of type [" +
ObjectUtils.nullSafeClassName(valueHolder.getValue()) +
"] to required type [" + paramType.getName() + "]: " + ex.getMessage());
}
此時我們可以注意到,在包裝成UnsatisfiedDependencyException例外的時候,只是把捕捉到的TypeMismatchException通過getMessage方法追加在例外描述后面,此時經過前面幾輪的包裝再包裝,真實的例外的例外資訊僅剩Unresolvable class definition for class [cn.howardliu.demo.AddressMapper]這段經過處理的資訊,完全沒有java.lang.NoClassDefFoundError的影子了,
至此,真實體外消失無蹤,
這也給我們一個提醒,我們要保證例外的時候,一定要保留有效資訊,否則,排錯會非常麻煩,
文末總結
本文是抓蟲文,從問題出發,到解決問題,給出完整的思路,java.lang.NoClassDefFoundError一般都是出現在版本沖突的時候,這種例外是編譯打包沒有問題,在運行時加載類失敗,在本文中之所以排查時走了一些彎路,是因為Spring隱藏了真實體外,給我們排錯造成了一些阻礙,所以,我們在日常開發時也要重視例外的明確資訊,可以給我們排錯提供準確的目標,
青山不改,綠水長流,我們下次見,
推薦閱讀
- 一文掌握 Java8 Stream 中 Collectors 的 24 個操作
- 一文掌握 Java8 的 Optional 的 6 種操作
- 使用 Lambda 運算式實作超強的排序功能
- Java8 的時間庫(1):介紹 Java8 中的時間類及常用 API
- Java8 的時間庫(2):Date 與 LocalDate 或 LocalDateTime 互相轉換
- Java8 的時間庫(3):開始使用 Java8 中的時間類
- Java8 的時間庫(4):檢查日期字串是否合法
- Java8 的新特性
- Java9 的新特性
你好,我是看山,游于碼界,戲享人生,如果文章對您有幫助,請點贊、收藏、關注,我還整理了一些精品學習資料,關注公眾號「看山的小屋」,回復“資料”即可獲得,
個人主頁:https://www.howardliu.cn
個人博文:Mybatis-Plus 版本沖突觸發“Could not convert argument value of type [java.lang.String] to required type [java.lang.Class]”的 java.lang.NoClassDefFoundError 例外
CSDN 主頁:https://kanshan.blog.csdn.net/
CSDN 博文:Mybatis-Plus 版本沖突觸發“Could not convert argument value of type [java.lang.String] to required type [java.lang.Class]”的 java.lang.NoClassDefFoundError 例外

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/397635.html
標籤:java
