最近因?yàn)轫?xiàng)目的原因薛夜,發(fā)現(xiàn)一個(gè)運(yùn)行時(shí)錯(cuò)誤:NoClassDefFoundError歪脏,對(duì)于這個(gè)常見(jiàn)的java Error進(jìn)行一下總結(jié)技潘。同時(shí)區(qū)分一下NoClassDefFoundError與ClassNotFoundException。
ClassNotFoundException
下面是一個(gè)ClassNotFoundException出現(xiàn)的場(chǎng)景分飞,當(dāng)使用JDBC去連接數(shù)據(jù)庫(kù)的時(shí)候悴务,一般會(huì)使用Class.forName()的方式去加載JDBC的驅(qū)動(dòng),如果沒(méi)有將驅(qū)動(dòng)放到應(yīng)用的classpath下譬猫,那么會(huì)導(dǎo)致運(yùn)行時(shí)找不到類讯檐,所以運(yùn)行Class.forName()會(huì)拋出ClassNotFoundException:
public class MainClass {
public static void main(String[] args) {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Log
java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at com.ebay.app.SampleResource.main(SampleResource.java:43)
NoClassDefFoundError
NoClassDefFoundError是一個(gè)運(yùn)行時(shí)異常,造成這個(gè)異常的原因染服,是在運(yùn)行過(guò)程中别洪,JVM無(wú)法找到運(yùn)行時(shí)需要的class文件。
為什么在編譯時(shí)能夠通過(guò)柳刮,但是運(yùn)行時(shí)卻無(wú)法找到對(duì)應(yīng)的class類挖垛。下面舉一個(gè)例子痒钝。
有三個(gè)類,DependencyA(A)晕换,DependencyB(B)午乓,DependencyC(C)站宗。這三個(gè)類是不同項(xiàng)目下的闸准。
其中依賴關(guān)系:
-
A依賴B,并使用B中的打印方法:
-
B中依賴C梢灭,并使用了C中的方法:
pom.xml 如下:
<dependencies>
<dependency>
<groupId>com.test</groupId>
<artifactId>C</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
-
C只是進(jìn)行一個(gè)簡(jiǎn)單的輸出:
NoClassDefFoundError
當(dāng)執(zhí)行A中的main方法時(shí)夷家,可以看到,正常打印了A中的語(yǔ)句和B中的語(yǔ)句敏释,但是在繼續(xù)運(yùn)行C的方法的時(shí)候库快,出現(xiàn)異常java.lang.NoClassDefFoundError: com/demo/c/DependencyC
-
Log
- Pom
由于demo中的代碼使用maven進(jìn)行依賴管理,需要考慮依賴問(wèn)題钥顽。當(dāng)查看pom文件配置后發(fā)現(xiàn)义屏,程序A中只引入了B所在的jar包,但是對(duì)于B中需要的C類的jar包沒(méi)有做依賴聲明蜂大。
<dependencies>
<dependency>
<groupId>com.test</groupId>
<artifactId>B</artifactId>
</dependency>
</dependencies>
- 分析
A的做法是標(biāo)準(zhǔn)的Maven的做法闽铐,但是由于B對(duì)C的依賴是provided。而對(duì)于scope=provided的情況奶浦,maven 不會(huì)管理C兄墅,因此在class path 就沒(méi)有C的jar。最常見(jiàn)的這類artifact是servlet-api.jar澳叉。
當(dāng)JVM在進(jìn)行類加載的過(guò)程中隙咸,A依賴B,加載B的時(shí)候成洗,從當(dāng)前的依賴的Jar中找到對(duì)應(yīng)的class文件五督,B中打印語(yǔ)句可以正常執(zhí)行。當(dāng)B依賴C瓶殃,JVM開(kāi)始加載C的過(guò)程中發(fā)現(xiàn)概荷,無(wú)法在當(dāng)前class path中找到對(duì)應(yīng)的class文件,所以在這種情況下碌燕,就會(huì)報(bào)出NoClassDefFoundError误证。
其它出現(xiàn)NoClassDefFoundError的形式
其實(shí)還有更簡(jiǎn)單的形式,例如編譯通過(guò)后修壕,手動(dòng)刪除引用的某個(gè)class文件愈捅;修改引用的jar包名稱。歸根結(jié)底慈鸠,NoClassDefFoundError出現(xiàn)的原因都是因?yàn)轭惣虞d過(guò)程中沒(méi)有辦法找到對(duì)應(yīng)的class文件蓝谨。
在源碼中也明確寫道,當(dāng)JVM或類加載器嘗試加載一個(gè)類時(shí),這個(gè)類對(duì)應(yīng)的class文件沒(méi)有找到時(shí)譬巫,JVM會(huì)報(bào)出當(dāng)前的錯(cuò)誤咖楣。
/**
* Thrown if the Java Virtual Machine or a <code>ClassLoader</code> instance
* tries to load in the definition of a class (as part of a normal method call
* or as part of creating a new instance using the <code>new</code> expression)
* and no definition of the class could be found.
* <p>
* The searched-for class definition existed when the currently
* executing class was compiled, but the definition can no longer be
* found.
*
* @author unascribed
* @since JDK1.0
*/
public
class NoClassDefFoundError extends LinkageError {
private static final long serialVersionUID = 9095859863287012458L;
/**
* Constructs a <code>NoClassDefFoundError</code> with no detail message.
*/
public NoClassDefFoundError() {
super();
}
/**
* Constructs a <code>NoClassDefFoundError</code> with the specified
* detail message.
*
* @param s the detail message.
*/
public NoClassDefFoundError(String s) {
super(s);
}
}
解決方案
以demo中演示的代碼為例,只需要在A所在的Project中引入C所在的依賴芦昔,即可解決當(dāng)前問(wèn)題:
NoClassDefFoundError與ClassNotFoundException
如果JVM或者ClassLoader在加載類時(shí)找不到對(duì)應(yīng)的類诱贿,就會(huì)引發(fā)NoClassDefFoundError和ClassNotFoundException。由于不同的ClassLoader會(huì)從不同的地方加載類咕缎,有時(shí)是錯(cuò)誤的CLASSPATH引發(fā)這類錯(cuò)誤珠十,有時(shí)是某個(gè)庫(kù)的jar包缺失引發(fā)這類錯(cuò)誤。NoClassDefFoundError和ClassNotFoundException之間存在一些細(xì)微的不同點(diǎn)凭豪。
NoClassDefFoundError
- 表示該類在編譯階段還可以找到焙蹭,但是在運(yùn)行Java應(yīng)用的時(shí)候找不到了,有時(shí)靜態(tài)塊的初始化過(guò)程會(huì)導(dǎo)致NoClassDefFoundError嫂伞。
- NoClassDefFoundError是Error孔厉,是unchecked,因此也不需要使用try-catch或者finally語(yǔ)句塊包圍帖努。
-
NoClassDefFoundError 是鏈接錯(cuò)誤撰豺,發(fā)生在鏈接階段,當(dāng)解析引用的時(shí)候找不到對(duì)應(yīng)的類然磷,就會(huì)拋出java.lang.NoClassDefFoundError郑趁;ClassNotFoundException是異常,發(fā)生在運(yùn)行階段姿搜。
常見(jiàn)Case
如果開(kāi)發(fā)中遇到NoClassDefFoundError寡润,那么最有可能的原因
- jar 包不存在
- 存在多個(gè)類加載器和多個(gè)目標(biāo)類,即我們常說(shuō)的Jar包沖突舅柜。比如說(shuō)上文的B 梭纹,如果有兩個(gè)版本的B存在于依賴中 中, B1是compile scope 致份, B2 是provided scope变抽,如果maven 解析到B1,就不會(huì)有NoClassDefFoundError的問(wèn)題氮块,但是解析到B2绍载,就有NoClassDefFoundError的問(wèn)題。這種情況迷惑性比較大滔蝉,第一次build 是可以的击儡,同樣的code 第二次build 就不可以了。非常難以定位蝠引。解決這種問(wèn)題阳谍,通常需要用mvn dependency tree 來(lái)查看到底引用了哪些同名的Artifact蛀柴,進(jìn)而exclude 錯(cuò)誤的artifacts。
ClassNotFoundException
- 和編譯期沒(méi)什么關(guān)系矫夯,當(dāng)你在代碼中顯式加載類(使用Class.forName())時(shí)沒(méi)有找到對(duì)應(yīng)的類鸽疾,則會(huì)拋出java.lang.ClassNotFoundException。例如加載SQL驅(qū)動(dòng)時(shí)训貌,對(duì)應(yīng)的類加載器找不到驅(qū)動(dòng)類制肮。
- ClassNotFoundException是受檢異常(checked Exception),因此需要使用try-catch語(yǔ)句塊或者try-finally語(yǔ)句塊包圍旺订,否則會(huì)導(dǎo)致編譯錯(cuò)誤
常見(jiàn)Case
調(diào)用Class.forName()弄企、ClassLoader.findSystemClass()和ClassLoader.loadClass()等方法時(shí)可能會(huì)引起java.lang.ClassNotFoundException超燃。