使用Classloader创建的对象无接口调用或反映Invoke

Use object created by Classloader without interface call or reflect invoke?

本文关键字:调用 Invoke 接口 Classloader 创建 对象 使用      更新时间:2023-10-16

我正在使用java做一些与C 动态库使用相同的事情。

我没有找到直接使用同一类对象的方法,而没有反射调用样式代码。

这是我的动态库代码,我将其制作为jar。

package com.demo;
public class Logic {
    public String doWork() {
        System.out.println("Hello from Dll");
        return "Dll";
    }
}

在我的主要应用程序中,我可以通过urlclassloader创建实例,通过反射调用很好:

public class Main {
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        File file = new File("C:\plugin.jar");
        URL url = file.toURI().toURL();
        URL[] urls = {url};
        ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
        ClassLoader loader = new URLClassLoader(urls, parentLoader);
        Thread.currentThread().setContextClassLoader(loader);
        Class<?> clazz = loader.loadClass("com.demo.Logic");
        System.out.println("New Instance!!");
        Object logic = clazz.newInstance();
        Method method = logic.getClass().getMethod("doWork");
        method.invoke(logic);
}

输出:

New Instance!!
Hello from Dll

但是,当我不使用反射Invoke而更改代码时:

public class Main {
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        File file = new File("C:\plugin.jar");
        URL url = file.toURI().toURL();
        URL[] urls = {url};
        ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
        ClassLoader loader = new URLClassLoader(urls, parentLoader);
        Thread.currentThread().setContextClassLoader(loader);
        Class<?> clazz = loader.loadClass("com.demo.Logic");
        Logic logic = (Logic)clazz.newInstance();
        logic.doWork();
    }
}

编译成功(使用外部模块编译),但是当我运行程序时,它在线路Logic logic = (Logic)clazz.newInstance();

上失败

例外:

Exception in thread "main" java.lang.NoClassDefFoundError: com/demo/Logic
    at Main.main(Main.java:31)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
Caused by: java.lang.ClassNotFoundException: com.demo.Logic
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
    ... 6 more

有什么办法可以使它起作用?没有反射/接口。(在C 中,我可以轻松实现此目标,共享相同的结构/类声明,请确保使用相同的编译器编译两个部分。ImhoJava也会做到这一点)


附加说明1

我想更改当前的classloader行为以使其识别动态加载类,这种尝试是简单而天真的,找不到其他方向:

        ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
        ClassLoader loader = new URLClassLoader(urls, parentLoader);
        Thread.currentThread().setContextClassLoader(loader);

为了使这项工作,您必须从逻辑上将类分为三组:

  1. 您的主要班级
  2. 您的插件类
  3. 您的插件依赖类

创建新的类加载程序时,必须确保类#2 #3的类都由同一类加载程序加载,因为urlclclassloader委托只朝着父级。这意味着加载主类的JVM应用程序类加载程序中的类无法在新类加载程序中看到类。为了像C这样的工作,您必须更新主要类的类路径,并且不支持这一点(这是可能的,但不是支持;我读过,我读过Java 9会删除此功能)。

实际上,您应该将主类分为两部分(#1和#3),然后使用反射来加载/调用插件依赖性类(一个反射调用,以及您的插件依赖性类别的类别可运行,您可以使用((Runnable)loadClass("PluginDependent").newInstance()).run()减少)。但是,您需要确保您的UrlClassloader不会委派插件依赖类的负载,例如:

  1. 将您的应用程序拆分为上面列出的三个离散集(main.jar,plugin.jar和main-plugin dipendent.jar),并将所有这些设置列在urllclassloader上。

  2. 更改URLCLASSLOADER的创建以指定显式无效父母,以便它不会委派给JVM应用程序类加载程序,然后指定plugin.jar.jar.jar 您的主罐子。

  3. 编写一个自定义的URLCLASSLOADER,该URLCLASSLOADER覆盖loadClass,以确保您的插件依赖类的类是由该类加载程序加载而不是委派给JVM应用程序类加载程序。

我用少量更改测试了您的代码(使用的默认类加载程序并将逻辑放置在我的类路径中)。这有效:

public class Main {
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
        Class<?> clazz = parentLoader.loadClass("com.demo.Logic");
        Logic logic = (Logic)clazz.newInstance();
        logic.doWork();
    }
}

您的问题在于从插件中检索课程。


更新:

我还尝试从第二个示例中的代码从jar中检索类。我将编译的Logic.Class放入com emo中,并用jar cvf plugin.jar .comdemoLogic.class构建了罐子,并与您相同的路径。它也没有问题。

另外,您无需将当前的线程类加载程序设置为加载程序。至少不是出于此示例的目的。

您的插件实际上可能不包含类。


更新到:

其他说明1

我想更改当前的classloader行为以使其识别 动态加载类,这种尝试是简单而天真的,找不到 其他方向:

ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
ClassLoader loader = new URLClassLoader(urls, parentLoader);
Thread.currentThread().setContextClassLoader(loader);

您正在使用子级装载机做正确的事情。

但是,如果要"更改当前的classloader行为",则应阅读此答案

classloader是不可变的;你应该不能 Willy-nilly在运行时添加类。

因此,您提出了一个子类装载机的解决方案。

这就是为什么我说"您不需要将当前的线程类加载程序设置为加载程序( Child class Loader )。"