Java добавить Classpaths во время выполнения - PullRequest
0 голосов
/ 26 апреля 2020

Есть много ответов на этот вопрос в stackoverflow? Но большинство приводят ClassLoader.getSystemClassLoader() к URLClassLoader, и это работает больше.

Классы должны быть найдены systemclassloader.

Есть ли другое решение?
- без перезапуска фляги
- без создания собственного загрузчика классов (в этом случае я должен заменить системный загрузчик своим собственным)

Отсутствующие классы / фляги должны быть добавлены в данный момент только при запуске, и я не хотел добавлять они в манифесте с "Classpath".
Я нашел Java Агент с premain-Method. Это также может отлично работать, но в этом случае я хочу запустить метод premain без вызова "java -javaagent: ... -jar ..."

В настоящее время я перезапускаю свою программу в начале с пропущенные пути к классам:

public class LibLoader {
    protected static List<File> files = new LinkedList<>();

    public static void add(File file) {
        files.add(file);
    }

    public static boolean containsLibraries() {
        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
        String[] classpaths = runtimeMxBean.getClassPath().split(System.getProperty("path.separator"));

        List<File> classpathfiles = new LinkedList<>();
        for(String string : classpaths) classpathfiles.add(new File(string));

        for(File file : files) {
            if(!classpathfiles.contains(file)) return false;
        }

        return true;
    }

    public static String getNewClassPaths() {
        StringBuilder builder = new StringBuilder();

        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
        builder.append(runtimeMxBean.getClassPath());

        for(File file : files) {
            if(builder.length() > 0) builder.append(System.getProperty("path.separator"));

            builder.append(file.getAbsolutePath());
        }

        return builder.toString();
    }

    public static boolean restartWithLibrary(Class<?> main, String[] args) throws IOException {
        if(containsLibraries()) return false;

        List<String> runc = new LinkedList<>();

        runc.add(System.getProperty("java.home") + "\\bin\\javaw.exe");

        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
        List<String> arguments = runtimeMxBean.getInputArguments();
        runc.addAll(arguments);

        File me = new File(LibLoader.class.getProtectionDomain().getCodeSource().getLocation().getPath());

        String classpaths = getNewClassPaths();
        if(!classpaths.isEmpty()) {
            runc.add("-cp");
            runc.add(classpaths);
        }

        if(me.isFile()) {
            runc.add("-jar");
            runc.add(me.getAbsolutePath().replace("%20", " "));
        } else {
            runc.add(main.getName());
        }

        for(String arg : args) runc.add(arg);

        ProcessBuilder processBuilder = new ProcessBuilder(runc);
        processBuilder.directory(new File("."));
        processBuilder.redirectOutput(Redirect.INHERIT);
        processBuilder.redirectError(Redirect.INHERIT);
        processBuilder.redirectInput(Redirect.INHERIT);
        Process process = processBuilder.start();

        try {
            process.waitFor();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }
}

Надеюсь, у кого-то есть лучшее решение.

Ответы [ 2 ]

1 голос
/ 26 апреля 2020

Проблема в том, что классы должны быть найдены в моей системе ClassLoader, а не в новой ClassLoader.

Похоже, что ваше текущее решение перезапустить JVM - единственный чистый способ сделать это.

Систему ClassLoader нельзя изменить, и вы не можете добавить дополнительные JAR-файлы в это во время выполнения.

(Если вы пытались использовать отражение, чтобы связываться со структурами данных системного загрузчика классов, в лучшем случае это будет непереносимо и зависит от версии. В худшем случае это будет либо подвержено ошибкам ..., либо заблокировано JVM механизмы безопасности во время выполнения.)

Решение, предложенное в комментарии Йоханнесом Куном, не будет работать. Свойство java.system.class.loader проверяется во время JVM bootstrap. К тому времени, когда ваше приложение работает, внесение изменений в него не будет иметь никакого эффекта. Я не убежден, что подход в его Ответе тоже сработает.


Вот один из возможных альтернативных способов справиться с этим ... если вы можете решить, какие недостающие JAR-файлы достаточно рано.

Напишите себе класс Launcher, который выполняет следующие действия:

  • Сохраните аргументы командной строки
  • Найдите файл JAR приложения
  • Извлеките атрибуты Main-Class и Class-Path из MANIFEST.MF.
  • Выясните, какой реальный путь к классу должен быть основан на вышеупомянутом ... и других спецификациях приложения c logi c.
  • Создайте новый URLClassLoader с правильным путем к классу и системным загрузчиком классов в качестве его родителя.
  • Используйте его для загрузки основного класса.
  • Используйте отражение, чтобы найти Основные классы main метод.
  • Вызывайте его, передавая аргументы командной строки сохранения.

По сути, это подход, который Spring Bootstrap и OneJar (и другие вещи) используют для обрабатывать про "баночки в банке" кровоизлияние и так далее. Это позволяет избежать запуска 2 виртуальных машин.

0 голосов
/ 26 апреля 2020

Вы сказали, что можете использовать агента для выполнения этой работы, но не хотите никаких дополнительных опций в командной строке java.

Что ж, возможно присоединиться к уже запущена виртуальная машина :

VirtualMachine.attach(pid).loadAgent("/path/to/agent.jar");

Проблема в том, что нельзя подключаться к вашей собственной виртуальной машине.

Чтобы обойти это, запустите новую, которая выполняет подключение. Вы можете передать PID своей собственной виртуальной машины в качестве аргумента.

Если вы не хотите самостоятельно писать код, ByteBuddy имеет функцию для него .

...