您好,登錄后才能下訂單哦!
自定義Classloader導致ClassCastException該怎么辦,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
java.lang.ClassCastException: cn.com.nightfield.Plugin cannot be cast to cn.com.nightfield.Plugin
相同的class
,竟然不能cast?這是什么鬼?
自定義類加載器(Classloader
)是很常見的,它可以讓我們從自定義的文件系統目錄,網絡甚至是數據庫的各種文件類型(jar
, war
, zip
等)中加載class
文件。 我們項目中使用了一個開源的類管理工具PF4J,來加載指定目錄下的class
文件。但奇怪的是,當我們把class
加載進來之后,將它強轉為目標類型,卻報了java.lang.ClassCastException
,兩者明明是同一個class
!
先說明,錯誤是跟自定義類加載器有關。上一個小demo來模擬一下上述錯誤:
package cn.com.nightfield.jvm.classloader; // 在class path下定義一個類 public class Plugin {}
package cn.com.nightfield.jvm.classloader; import java.net.URL; import java.net.URLClassLoader; // 自定義一個類加載器 public class CustomizedClassLoader extends URLClassLoader { public CustomizedClassLoader(URL[] urls) { super(urls); } protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 如果不是自定義目錄下的class,統一委托給AppClassloader去加載 if (!name.startsWith("cn.com.nightfield.jvm.classloader")) { return super.loadClass(name, resolve); } // 如果是自定義目錄下的class,直接加載,此處違反了雙親委派模型 else { Class<?> c = findClass(name); if (resolve) { resolveClass(c); } return c; } } } }
package cn.com.nightfield.jvm.classloader; import java.io.File; import java.net.MalformedURLException; import java.net.URL; public class ClassLoaderTest { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, MalformedURLException { // 指定類加載器的加載路徑 URL url = new File("/Users/zhochi/demo/target/classes").toURI().toURL(); ClassLoader customizedClassLoader = new CustomizedClassLoader(new URL[]{url}); // 用自定義類加載器加載Plugin class Class clz = customizedClassLoader.loadClass("cn.com.nightfield.jvm.classloader.Plugin"); System.out.println(clz.getClassLoader()); Object pluginInstance = clz.newInstance(); // pluginInstance instanceof Plugin”輸出false System.out.println("pluginInstance instanceof Plugin: " + (pluginInstance instanceof Plugin)); // 報java.lang.ClassCastException錯誤 Plugin plugin = (Plugin) clz.newInstance(); } }
控制臺輸出如下:
cn.com.nightfield.jvm.classloader.CustomizedClassLoader@60e53b93 pluginInstance instanceof Plugin: false Exception in thread "main" java.lang.ClassCastException: cn.com.nightfield.jvm.classloader.Plugin cannot be cast to cn.com.nightfield.jvm.classloader.Plugin at cn.com.nightfield.jvm.classloader.ClassLoaderTest.main(ClassLoaderTest.java:19)
要想知道錯誤的根源,需要了解對象可以被cast的前提:對象必須是目標類的實例。從上述輸出也可以看到,instance instanceof Plugin
的結果是false
,為什么呢?因為對于任意一個類,都需要由它的類加載器和這個類本身,共同確立其在JVM
中的唯一性,也就是說,JVM
中兩個類是否相等,首先要看它們是不是由同一個類加載器加載的。如果不是的話,即使這兩個類來自于同一個class
文件,它們也不相等。
上例中,Plugin
類處于class path
下,默認是由AppClassloader
來加載的;但是pluginInstance
卻是由CustomizedClassLoader
加載出來的class
的實例。JVM
嘗試將CustomizedClassLoader.Plugin
轉成AppClassloader.Plugin
,必然會報錯。
其實究其原因,是我們在自定義類加載器CustomizedClassLoader
中,違反了雙親委派模型。 我們都知道,Java
中有三大類加載器:BootstrapClassLoader
,ExtClassLoader
和AppClassLoader
,它們在組合上構成父子關系,前者是后者的"父親",并且有各自的“領地”:BootstrapClassLoader
負責加載 Java
核心類庫如JRE
中的rt.jar
,resource.jar
;ExtClassLoader
負責加載{java.home}/lib/ext
和java.ext.dirs
系統目錄下的class
;AppClassLoader
則是加載class path
路徑下,也就是我們自己寫的class
文件。 所謂雙親委派模型,指的是當Classloader
收到一個加載class
請求的時候,首先會委托給其父親去加載,如果父親加載不成功,自己才會嘗試去加載。雙親委派的機制是JVM
中類的安全性的一大保障:就算有人惡意自定義了一個String.class
,最終由類加載器加載到的依然是rt.jar
中的String
。以下是loadClass
的部分源碼:
public abstract class ClassLoader { protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 1. 如果類已經被加載過了,直接返回 Class<?> c = findLoadedClass(name); if (c == null) { try { // 2. 委托父類去加載 if (parent != null) { c = parent.loadClass(name, false); } else { // 這種情況指的就是委托BootstrapClassLoader去加載 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // 3. 嘗試自己加載 c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } }
不過,雙親委派模型并不是一個強制的約束,而是Java
推薦的模式,所以我們在自定義類加載器的時候,推薦重寫findClass()
方法,而不是loadClass()
方法。
回到最開始的問題,分析了一下PF4J
的源碼,可以猜到,它也定義了自己的類加載器PluginClassLoader,且它重寫的loadClass()
方法的默認實現,為了防止class
的版本問題,違反了雙親委派模型。
Java
中的類加載器,相當于是其加載的class
的命名空間,兩個類相等,首先要保證它們是由同一個類加載器加載的。 在實現自定義類加載器的時候,除非你對類加載機制有著深刻的認知且知道自己在做什么,否則不要違反雙親委派模型。
關于自定義Classloader導致ClassCastException該怎么辦問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。