Использовать объект, созданный Classloader без вызова интерфейса или отражать вызов?

Я использую Java, чтобы сделать что-то такое же, как использование динамической библиотеки C ++.

Я не нашел способ напрямую использовать тот же объект класса без отражения кода стиля вызова.

это мой код динамической библиотеки, я делаю это банкой.

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

Но когда я изменяю код без использования Reflect, вызывайте:

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 ++ я могу легко добиться этого, разделить одно и то же объявление структуры / класса, удостовериться, что один и тот же компилятор компилирует две части. ИМХО Java сделал бы это также)


дополнительное объяснение 1

Я хочу изменить текущее поведение загрузчика классов, чтобы он распознал динамически загруженный класс, эта попытка проста и наивна, не может найти другое направление:

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

1

Решение

Чтобы сделать это, вы должны логически разделить классы на три набора:

  1. Ваш основной класс
  2. Ваши плагин классы
  3. Ваши плагин-зависимые классы

Когда вы создаете новый загрузчик классов, вы должны убедиться, что классы # 2 а также # 3 оба загружаются одним и тем же загрузчиком классов, потому что делегирование URLClassLoader идет только к родителю. Это означает, что классы в загрузчике классов приложения JVM, который загрузил ваш основной класс, не могут видеть класс в вашем новом загрузчике классов. Чтобы работать как C, вы должны обновить путь к классу вашего основного класса, и это не поддерживается (возможно, но это не так поддержанный; Я читал, что Java 9 удалит эту возможность).

На практике вы должны разделить ваш основной класс на две части (# 1 и # 3), а затем использовать отражение для загрузки / вызова зависимого от плагина класса (один вызов отражения, и если ваш зависимый от плагина класс реализует Runnable, вы можете использование ((Runnable)loadClass("PluginDependent").newInstance()).run() чтобы уменьшить даже это). Однако вы должны убедиться, что ваш URLClassLoader не делегирует загрузку зависимого от плагина класса, например:

  1. Разделите ваше приложение на три отдельных набора, перечисленных выше (main.jar, plugin.jar и main-plugin-зависимый.jar), и перечислите все их в URLClassLoader.

  2. Измените создание URLClassLoader, чтобы указать явный нулевой родительский элемент, чтобы он не делегировал загрузчик классов приложения JVM, а затем укажите оба plugin.jar. а также Ваш главный JAR.

  3. Напишите пользовательский URLClassLoader, который переопределяет loadClass, чтобы гарантировать, что ваши зависимые от плагина классы загружаются этим загрузчиком классов, а не делегируются загрузчику классов приложения JVM.

2

Другие решения

Я протестировал ваш код с небольшим изменением (использовал загрузчик классов по умолчанию и поместил логику в мой путь к классам).
Это работает:

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

Ваша проблема заключается в получении класса из plugin.jar.


Обновлено:

Я также попытался извлечь класс из фляги с кодом из второго примера.
Я поместил скомпилированный Logic.class в com \ demo и собрал банку с jar cvf plugin.jar .\com\demo\Logic.class и поставить на тот же путь, что и вы.
Это работало без проблем также.

Кроме того, вам не нужно устанавливать загрузчик текущего класса потока в загрузчик.
По крайней мере, не для целей этого примера.

Ваш plugin.jar может на самом деле не содержать класс.


Обновление до:

дополнительное объяснение 1

Я хочу изменить текущее поведение загрузчика классов, чтобы он распознал
динамически загруженный класс, эта попытка проста и наивна, не может найти
другое направление:

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

Вы делаете это правильно с загрузчиком дочернего класса.

Но если вы хотите «изменить текущее поведение загрузчика классов», вы должны прочитать этот ответ

Загрузчики классов должны быть неизменными; ты не должен быть в состоянии
волей-неволей добавить классы к нему во время выполнения.

Таким образом, решение, которое вы пришли с загрузчиком дочернего класса.

Вот почему я сказал: «Вам не нужно устанавливать загрузчик текущего класса потока в loader (детский класс загрузчик) «.

1