溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

自定義Classloader導(dǎo)致ClassCastException該怎么辦

發(fā)布時間:2021-12-14 17:45:29 來源:億速云 閱讀:294 作者:柒染 欄目:大數(shù)據(jù)

自定義Classloader導(dǎo)致ClassCastException該怎么辦,針對這個問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

背景

java.lang.ClassCastException: cn.com.nightfield.Plugin cannot be cast to cn.com.nightfield.Plugin

相同的class,竟然不能cast?這是什么鬼?

問題描述

自定義類加載器(Classloader)是很常見的,它可以讓我們從自定義的文件系統(tǒng)目錄,網(wǎng)絡(luò)甚至是數(shù)據(jù)庫的各種文件類型(jar, war, zip等)中加載class文件。 我們項(xiàng)目中使用了一個開源的類管理工具PF4J,來加載指定目錄下的class文件。但奇怪的是,當(dāng)我們把class加載進(jìn)來之后,將它強(qiáng)轉(zhuǎn)為目標(biāo)類型,卻報(bào)了java.lang.ClassCastException,兩者明明是同一個class!

問題分析

先說明,錯誤是跟自定義類加載器有關(guān)。上一個小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,統(tǒng)一委托給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));
        // 報(bào)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的前提:對象必須是目標(biāo)類的實(shí)例。從上述輸出也可以看到,instance instanceof Plugin的結(jié)果是false,為什么呢?因?yàn)閷τ谌我庖粋€類,都需要由它的類加載器和這個類本身,共同確立其在JVM中的唯一性,也就是說,JVM中兩個類是否相等,首先要看它們是不是由同一個類加載器加載的。如果不是的話,即使這兩個類來自于同一個class文件,它們也不相等。

上例中,Plugin類處于class path下,默認(rèn)是由AppClassloader來加載的;但是pluginInstance卻是由CustomizedClassLoader加載出來的class的實(shí)例。JVM嘗試將CustomizedClassLoader.Plugin轉(zhuǎn)成AppClassloader.Plugin,必然會報(bào)錯。

問題解決

其實(shí)究其原因,是我們在自定義類加載器CustomizedClassLoader中,違反了雙親委派模型。 我們都知道,Java中有三大類加載器:BootstrapClassLoader,ExtClassLoaderAppClassLoader,它們在組合上構(gòu)成父子關(guān)系,前者是后者的"父親",并且有各自的“領(lǐng)地”:BootstrapClassLoader負(fù)責(zé)加載 Java核心類庫如JRE中的rt.jar,resource.jarExtClassLoader負(fù)責(zé)加載{java.home}/lib/extjava.ext.dirs系統(tǒng)目錄下的class;AppClassLoader則是加載class path路徑下,也就是我們自己寫的class文件。 所謂雙親委派模型,指的是當(dāng)Classloader收到一個加載class請求的時候,首先會委托給其父親去加載,如果父親加載不成功,自己才會嘗試去加載。雙親委派的機(jī)制是JVM中類的安全性的一大保障:就算有人惡意自定義了一個String.class,最終由類加載器加載到的依然是rt.jar中的String。以下是loadClass的部分源碼:

public abstract class ClassLoader {
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 1. 如果類已經(jīng)被加載過了,直接返回
            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;
        }
    }

不過,雙親委派模型并不是一個強(qiáng)制的約束,而是Java推薦的模式,所以我們在自定義類加載器的時候,推薦重寫findClass()方法,而不是loadClass()方法。

回到最開始的問題,分析了一下PF4J的源碼,可以猜到,它也定義了自己的類加載器PluginClassLoader,且它重寫的loadClass()方法的默認(rèn)實(shí)現(xiàn),為了防止class的版本問題,違反了雙親委派模型。

Java中的類加載器,相當(dāng)于是其加載的class的命名空間,兩個類相等,首先要保證它們是由同一個類加載器加載的。 在實(shí)現(xiàn)自定義類加載器的時候,除非你對類加載機(jī)制有著深刻的認(rèn)知且知道自己在做什么,否則不要違反雙親委派模型。

關(guān)于自定義Classloader導(dǎo)致ClassCastException該怎么辦問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI