简体   繁体   English

Java ClassLoaders - 将 class 转换为接口

[英]Java ClassLoaders - Cast class to an interface

I'm trying to develop a java application in which users can add functionality (through plugins) which must implement a common interface: PluginFunction.我正在尝试开发一个 java 应用程序,用户可以在其中添加必须实现通用接口的功能(通过插件):PluginFunction。 Then, the custom.class files must be in a specified directory (in this case, ./plugins) and are loaded by my custom classloader.然后,custom.class 文件必须位于指定目录(在本例中为 ./plugins)并由我的自定义类加载器加载。

While tesing from the IDE everything works fine, but when I export the app to a jar file the following exception is thrown:从 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)

I've been doing some research and I found out the problem occurs because the interface is loaded by a different classloader than my custom classloader, but I don't know how to fix this.我一直在做一些研究,发现问题是因为接口是由与我的自定义类加载器不同的类加载器加载的,但我不知道如何解决这个问题。

Thanks谢谢

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. 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.

Usually, we java devs say that a type (class, interface, enum, etc) is only ever loaded once by the system (as in, there is just one instance of java.lang.Class ).通常,我们 java 开发人员说,一个类型(类、接口、枚举等)只被系统加载一次(例如,只有一个java.lang.Class实例)

This is true.这是真实的。

However, 'a class', that definition needs some fudging: A class is defined not just by its fully qualified name ( operaciones.Suma ).但是,“一个类”,该定义需要一些捏造:class不仅由其完全限定名称( operaciones.Suma )定义。 It's actually defined by the combination of its name and its loader.它实际上是由它的名称和它的加载器的组合定义的。

Now, in the normal situation, you have only one relevant loader present in a given VM: it loaded the class that had your main method and will load everything that executing this method ends up needing, and it looks at the classpath to do its job.现在,在正常情况下,给定虚拟机中只有一个相关的加载器:它加载了具有您的 main 方法的 class 并将加载执行此方法最终需要的所有内容,并且它查看类路径来完成它的工作.

Important: Compatibility of instances重要:实例的兼容性

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. 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. You get crazy errors, like "Instance of type java.lang.Integer cannot be assigned to variable of type java.lang.Integer".您会遇到疯狂的错误,例如“java.lang.Integer 类型的实例不能分配给 java.lang.Integer 类型的变量”。

Important: The notion of boundary types重要:边界类型的概念

In your module system, there are 3 worlds.在您的模块系统中,有 3 个世界。 There's the stuff you do, internally, in your main app.在您的主应用程序中,您可以在内部执行这些操作。 The plugin has no business even knowing about any of this.该插件甚至不知道这一切。 It's stuff marked private and the like.它的东西标记为私人等。

In the plugin system, that too has private components.在插件系统中,它也有私有组件。

But, there's a third world: What's in between the two.但是,还有第三个世界:两者之间是什么。 Presumably, you are going to end up having a string generated in your main app code, and you hand it over to the plugin.据推测,您最终会在主应用程序代码中生成一个字符串,然后将其交给插件。 That means java.lang.String is a boundary type.这意味着java.lang.String是边界类型。 The plugin has operaciones.PluginOperacion as a thing it needs (it implements it, after all,).该插件将operaciones.PluginOperacion作为它需要的东西(毕竟它实现了它)。 but so does your main code, That, too.但是您的主要代码也是如此。 is a boundary type.是边界类型。

Crucial point: All boundary types must be loaded by one class loader (both for the plugin code and the main app), because otherwise you can't use them as boundary type.关键点:所有边界类型都必须由一个 class 加载程序(插件代码和主应用程序)加载,否则您不能将它们用作边界类型。

Thus, the explanation of what you are observing is simply: The plugin ended up loading a boundary type in its own loader, instead of in loader of your main class, and that's what you need to fix.因此,您所观察到的解释很简单:插件最终在其自己的加载器中加载了边界类型,而不是在您的主 class 的加载器中,这就是您需要修复的。

Important: "What is the loader?"重要提示:“什么是装载机?”

What does "the loader that loaded this class" mean? “加载这个类的加载器”是什么意思? It is simple: ClassLoaders, in the very end, call the native defineClass method on themselves, passing a byte array of bytecode.很简单:ClassLoader 最终调用自己的本地defineClass方法,传递字节码的字节数组。 THAT makes the instance you call the defineClass method on "the loader".这使得您在“加载器”上调用defineClass方法的defineClass Whenever that class ends up needing another class to do its job, it will immediately ask its class loader to do so.每当 class 最终需要另一个 class 来完成它的工作时,它会立即要求它的 class 加载器这样做。 This is true even for something as simple as java.lang.String .即使对于像java.lang.String这样简单的东西也是如此。

Important: ClassLoaders have parentage重要:ClassLoader 有父级

The ClassLoader API is designed to be fairly flexible, but its most basic intended usage is as follows: ClassLoader API 设计得相当灵活,但其最基本的用途如下:

  1. ClassLoaders have parent loaders.类加载器有父加载器。
  2. To load any resource, the CL will first ask its parents to load it .要加载任何资源,CL 将首先要求其父级加载它 They call defineClass , making the class it loaded set up such that your parent is the loader, not your custom loader.他们调用defineClass ,使其加载的 class 设置为您的父级是加载器,而不是您的自定义加载器。
  3. Only if the parent(s) can't get it done, will the CL do it itself.只有当父母无法完成时,CL才会自己完成。 You end up calling defineClass , you are the loader now.你最终调用了defineClass ,你现在是加载器。 Any types needed by what you loaded go from #1, and will again result in 'ask parent first, only if it cannot, we load it'.您从 #1 加载 go 所需的任何类型,并且将再次导致“先询问父母,只有当它不能时,我们才加载它”。

You don't have to, you can choose not to ask your parents and always load it itself.您不必这样做,您可以选择不问您的父母并始终自行加载。 There are reasons to do so sometimes.有时这样做是有原因的。

The right design正确的设计

So, the trick is, to use the parentage system to ensure that the boundary types are only ever loaded once.所以,诀窍是,使用父系系统来确保边界类型只加载一次。 Possibly by the one classloader you set up for your main app, but that's probably overengineering it: Your main app and ALL the boundary classes should be loaded by the java standard loader (that loaded the class with your main method), and the plugin itself and any types that it defines are loaded by the plugin's loader.可能是由您为主应用程序设置的一个类加载器,但这可能是过度设计它:您的主应用程序和所有边界类应由 java 标准加载器(使用您的 main 方法加载 class)和插件本身加载它定义的任何类型都由插件的加载器加载。

Because of parentage, this works out: The plugin's loader has a parent (the main loader).由于父级,这解决了:插件的加载器有一个父级(主加载器)。 You ask your custom loader to load the plugin.您要求自定义加载程序加载插件。 It asks its parent (main loader) first, but that can't find it because this plugin is not in your classpath.它首先询问其父级(主加载器),但由于此插件不在您的类路径中,所以找不到它。 Thus, pluginloader loads it.因此,pluginloader 加载它。 The very moment pluginloader has done this, pluginloader is immediately asked to then load operaciones.PluginOperacion because the plugin class you are loading extends/implements that.在 pluginloader 完成此操作的那一刻,pluginloader 立即被要求加载operaciones.PluginOperacion ,因为您正在加载的插件 class 扩展/实现了它。

Following the standard intent of the API, your pluginloader will ask its parent to load it, and... that should succeed , and thus the jlClass instance your pluginloader returns is nevertheless loaded by mainloader.按照 API 的标准意图,您的 pluginloader 将要求其父级加载它,并且...应该成功,因此您的 pluginloader 返回的jlClass实例仍然由 mainloader 加载。 As in, calling getClassLoader() on that will return the same thing YourMainApp.class.getClassLoader() returns.如,调用getClassLoader()将返回相同的东西YourMainApp.class.getClassLoader()返回。

Great?伟大的? How?如何?

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);
    }
}

That's all you have to do.这就是你所要做的。 Simple, once you grok how it works.很简单,一旦你了解它是如何工作的。

But, note that java itself is going to invoke loadClass and not findClass .但是,请注意 java 本身将调用loadClass而不是findClass Fortunately, the impl of loadClass that you inherit will ask parent first, and only if parent fails to find, will it invoke findClass (which will end up running the overridden code above).幸运的是,你继承的loadClass的 impl 会首先询问 parent,只有当 parent 找不到时,才会调用findClass (最终会运行上面覆盖的代码)。 Thus, if you want to write a classloader that doesn't fit the standard intent of asking parent first, you override loadClass instead.因此,如果您想编写一个不符合首先询问父母的标准意图的类加载器,您可以重写 loadClass。 But, the standard intent is usually what you want, so, usually, override findClass, not loadClass.但是,标准意图通常是您想要的,因此通常覆盖 findClass,而不是 loadClass。

To use:要使用:

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();
    }
}

Good luck with the rest of the project!祝项目rest好运!

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

相关问题 Java转换为类的接口 - Java cast interface to class 异常 class jdk.internal.loader.ClassLoaders$AppClassLoader 无法转换为 class java.net.URLClassLoader - Exception class jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to class java.net.URLClassLoader 基于 Java 的 Azure 函数 - 异常:ClassCastException:无法转换 class jdk.internal.loader.ClassLoaders$AppClassLoader - Java based Azure Function - Exception: ClassCastException: class jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast HIVE:异常:class jdk.internal.loader.ClassLoaders$AppClassLoader 无法在 Mac 上使用 JDK8 转换为 class java.net.URLClassLoader - HIVE: Exception: class jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to class java.net.URLClassLoader using JDK8 on Mac java:类加载器 - java : ClassLoaders 为什么转换为接口而不是转换为类 java? - Why cast to an interface instead of casting to a class java? 泛型无法将Object强制转换为接口类Java - Generics cannot cast Object to interface class java Java:您可以将 Class 转换为特定的接口吗? - Java: can you cast Class into a specific interface? Java Classloaders-在私有类内调用静态方法 - Java Classloaders - Invoking a static method inside a private class 无法在Java接口上强制转换 - Unsolvable cast on Java interface
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM