繁体   English   中英

Java ClassLoaders - 将 class 转换为接口

[英]Java ClassLoaders - Cast class to an interface

我正在尝试开发一个 java 应用程序,用户可以在其中添加必须实现通用接口的功能(通过插件):PluginFunction。 然后,custom.class 文件必须位于指定目录(在本例中为 ./plugins)并由我的自定义类加载器加载。

从 IDE 进行测试时,一切正常,但是当我将应用程序导出到 jar 文件时,会引发以下异常:

java.lang.ClassCastException: class operaciones.Suma cannot be cast to class operaciones.PluginOperacion (operaciones.Suma is in unnamed module of loader logica.PluginClassLoader @daf4db4; operaciones.PluginOperacion is in unnamed module of loader java.net.URLClassLoader @6576fe71)
        at logica.OperacionesManager.loadOperaciones(OperacionesManager.java:75)
        at gui.commands.RefreshCommand.execute(RefreshCommand.java:28)
        at gui.GUI_CalcSimple.<init>(GUI_CalcSimple.java:124)
        at gui.GUI_CalcSimple$1.run(GUI_CalcSimple.java:60)

我一直在做一些研究,发现问题是因为接口是由与我的自定义类加载器不同的类加载器加载的,但我不知道如何解决这个问题。

谢谢

NB: Because java represents all type kinds (classes, interfaces, enums, etc) java.lang.Class I'll use the term 'class' for the rest of this answer, but that encompasses interfaces and enums and such as well.

通常,我们 java 开发人员说,一个类型(类、接口、枚举等)只被系统加载一次(例如,只有一个java.lang.Class实例)

这是真实的。

但是,“一个类”,该定义需要一些捏造:class不仅由其完全限定名称( operaciones.Suma )定义。 它实际上是由它的名称和它的加载器的组合定义的。

现在,在正常情况下,给定虚拟机中只有一个相关的加载器:它加载了具有您的 main 方法的 class 并将加载执行此方法最终需要的所有内容,并且它查看类路径来完成它的工作.

重要:实例的兼容性

If you somehow end up with 2 separate loaded classes both named, say, java.lang.Integer , by using 2 class loaders that each individually loaded that class, then these types are completely incompatible. 您会遇到疯狂的错误,例如“java.lang.Integer 类型的实例不能分配给 java.lang.Integer 类型的变量”。

重要:边界类型的概念

在您的模块系统中,有 3 个世界。 在您的主应用程序中,您可以在内部执行这些操作。 该插件甚至不知道这一切。 它的东西标记为私人等。

在插件系统中,它也有私有组件。

但是,还有第三个世界:两者之间是什么。 据推测,您最终会在主应用程序代码中生成一个字符串,然后将其交给插件。 这意味着java.lang.String是边界类型。 该插件将operaciones.PluginOperacion作为它需要的东西(毕竟它实现了它)。 但是您的主要代码也是如此。 是边界类型。

关键点:所有边界类型都必须由一个 class 加载程序(插件代码和主应用程序)加载,否则您不能将它们用作边界类型。

因此,您所观察到的解释很简单:插件最终在其自己的加载器中加载了边界类型,而不是在您的主 class 的加载器中,这就是您需要修复的。

重要提示:“什么是装载机?”

“加载这个类的加载器”是什么意思? 很简单:ClassLoader 最终调用自己的本地defineClass方法,传递字节码的字节数组。 这使得您在“加载器”上调用defineClass方法的defineClass 每当 class 最终需要另一个 class 来完成它的工作时,它会立即要求它的 class 加载器这样做。 即使对于像java.lang.String这样简单的东西也是如此。

重要:ClassLoader 有父级

ClassLoader API 设计得相当灵活,但其最基本的用途如下:

  1. 类加载器有父加载器。
  2. 要加载任何资源,CL 将首先要求其父级加载它 他们调用defineClass ,使其加载的 class 设置为您的父级是加载器,而不是您的自定义加载器。
  3. 只有当父母无法完成时,CL才会自己完成。 你最终调用了defineClass ,你现在是加载器。 您从 #1 加载 go 所需的任何类型,并且将再次导致“先询问父母,只有当它不能时,我们才加载它”。

您不必这样做,您可以选择不问您的父母并始终自行加载。 有时这样做是有原因的。

正确的设计

所以,诀窍是,使用父系系统来确保边界类型只加载一次。 可能是由您为主应用程序设置的一个类加载器,但这可能是过度设计它:您的主应用程序和所有边界类应由 java 标准加载器(使用您的 main 方法加载 class)和插件本身加载它定义的任何类型都由插件的加载器加载。

由于父级,这解决了:插件的加载器有一个父级(主加载器)。 您要求自定义加载程序加载插件。 它首先询问其父级(主加载器),但由于此插件不在您的类路径中,所以找不到它。 因此,pluginloader 加载它。 在 pluginloader 完成此操作的那一刻,pluginloader 立即被要求加载operaciones.PluginOperacion ,因为您正在加载的插件 class 扩展/实现了它。

按照 API 的标准意图,您的 pluginloader 将要求其父级加载它,并且...应该成功,因此您的 pluginloader 返回的jlClass实例仍然由 mainloader 加载。 如,调用getClassLoader()将返回相同的东西YourMainApp.class.getClassLoader()返回。

伟大的? 如何?

class PluginLoader extends ClassLoader {
    public PluginLoader(ClassLoader parent) {
        super(parent);
    }

    public Class<?> findClass(String name) {
        String tgt = name.replace(".", "/") + ".class";
        byte[] bytecode = readFullyFromPluginjar(tgt);
        return defineClass(name, bytecode, 0, bytecode.length);
    }
}

这就是你所要做的。 很简单,一旦你了解它是如何工作的。

但是,请注意 java 本身将调用loadClass而不是findClass 幸运的是,你继承的loadClass的 impl 会首先询问 parent,只有当 parent 找不到时,才会调用findClass (最终会运行上面覆盖的代码)。 因此,如果您想编写一个不符合首先询问父母的标准意图的类加载器,您可以重写 loadClass。 但是,标准意图通常是您想要的,因此通常覆盖 findClass,而不是 loadClass。

要使用:

class Main {
    public PluginOperaciones loadPlugin(Path jarLocation, String className) {
        PluginLoader loader = new PluginLoader(Main.class.getClassLoader());
        loader.setJarSearchSpace(jarLocation);
        Class<?> pl = loader.loadClass(className); // load, not find!!
        return (PluginOperaciones) pl.getConstructor().newInstance();
    }
}

祝项目rest好运!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM