千家信息网

自定义Classloader导致ClassCastException该怎么办

发表于:2024-10-01 作者:千家信息网编辑
千家信息网最后更新 2024年10月01日,自定义Classloader导致ClassCastException该怎么办,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。背景jav
千家信息网最后更新 2024年10月01日自定义Classloader导致ClassCastException该怎么办

自定义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@60e53b93pluginInstance instanceof Plugin: falseException 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中有三大类加载器:BootstrapClassLoaderExtClassLoaderAppClassLoader,它们在组合上构成父子关系,前者是后者的"父亲",并且有各自的"领地":BootstrapClassLoader负责加载 Java核心类库如JRE中的rt.jarresource.jarExtClassLoader负责加载{java.home}/lib/extjava.ext.dirs系统目录下的classAppClassLoader则是加载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该怎么办问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注行业资讯频道了解更多相关知识。

问题 双亲 委派 文件 模型 目录 方法 错误 委托 两个 时候 分析 尝试 输出 怎么办 也就是 实例 对象 是由 更多 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 东营市工商 网络安全等级 宜兴现代软件开发服务电话 网络安全法是一部基础性 网络安全知识宣传活动的启示 嵌套查询用什么数据库 access数据库查询和平路 华为资源管理服务器 梦幻西游哪些服务器可以转珍宝阁 近年关于网络安全的法律法规 更新数据库控件类型 服务器爱快 网络安全问题会带来哪些影响 win2008数据库重装 惠州培训网络技术学校 c 登录界面不连接数据库 电脑服务器主板坏了可以修么 构建表空间导入数据库 ssrf网络安全测试 微信怎样修改建立新的数据库 睢县百事通网络技术有限公司 万维网服务器的安全配置 开发数据库应用系统有什么优势 二维码打印机怎么连接数据库 全国最大的跑腿服务软件开发代理 数据库触发器是一类靠 衢州云软件开发需要学什么 iphone13 为什么连接不了服务器 afs软件开发模型 服务器的日志怎么看 享域混动车的参考数据库
0