Удалить папку из Java classpath во время выполнения

Существует ли способ удаления папки из пути к классам, аналогичный добавлению папки во время выполнения ( Можно ли добавить каталог в путь к классам во время выполнения?)

5 ответов

Ниже приведен фрагмент кода в качестве технического примера, демонстрирующего добавление / удаление пути.

создать следующие исходные файлы в любом каталоге

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Stack;
import sun.misc.URLClassPath;

public class EvilPathDemo {

    public static void addPath(String path) throws Exception {
        URL u = new File(path).toURI().toURL();
        URLClassLoader urlClassLoader = (URLClassLoader)
            ClassLoader.getSystemClassLoader();
        Class<?> urlClass = URLClassLoader.class;
        Method method = urlClass.getDeclaredMethod("addURL",
                new Class[]{URL.class}
        );
        method.setAccessible(true);
        method.invoke(urlClassLoader, new Object[]{u});
    }

    public static void removePath(String path) throws Exception {
        URL url = new File(path).toURI().toURL();
        URLClassLoader urlClassLoader = (URLClassLoader) 
            ClassLoader.getSystemClassLoader();
        Class<?> urlClass = URLClassLoader.class;
        Field ucpField = urlClass.getDeclaredField("ucp");
        ucpField.setAccessible(true);
        URLClassPath ucp = (URLClassPath) ucpField.get(urlClassLoader);
        Class<?> ucpClass = URLClassPath.class;
        Field urlsField = ucpClass.getDeclaredField("urls");
        urlsField.setAccessible(true);
        Stack urls = (Stack) urlsField.get(ucp);
        urls.remove(url);
    }

    public static void main(String[] args) throws Exception {
        String parm = args.length == 1 ? args[0] : "";
        String evilPath = "/tmp";

        String classpath = System.getProperty("java.class.path");
        boolean isEvilPathSet = false;
        for (String path : classpath.split(File.pathSeparator)) {
            if (path.equalsIgnoreCase(evilPath)) {
                System.out.printf("evil path '%s' in classpath%n", evilPath);
                isEvilPathSet = true;
                break;
            }
        }
        if (isEvilPathSet && parm.equalsIgnoreCase("REMOVE")) {
            System.out.printf("evil path '%s' will be removed%n", evilPath);
            removePath(evilPath);
        }
        tryToLoad("Foo");
        if (parm.equalsIgnoreCase("ADD")) {
            System.out.printf("evil path '%s' will be added%n", evilPath);
            addPath(evilPath);
        }
        tryToLoad("Bar");
    }

    private static void tryToLoad(String className) {
        try {
            Class<?> foo = Class.forName(className);
            System.out.printf("class loaded: %s%n", foo.getName());
        } catch (ClassNotFoundException ex) {
            System.out.println(ex);
        }
    }
}

,

public class Foo {
    static {
        System.out.println("I'm foo...");
    }
}

,

public class Bar {
    static {
        System.out.println("I'm bar...");
    }
}

скомпилируйте их следующим образом

javac EvilPathDemo.java
javac -d /tmp Foo.java Bar.java

Во время теста мы попытаемся загрузить классы Foo а также Bar,

без / tmp в пути к классам

java -cp . EvilPathDemo
java.lang.ClassNotFoundException: Foo
java.lang.ClassNotFoundException: Bar

добавление / tmp к пути к классам

java -cp . EvilPathDemo add
java.lang.ClassNotFoundException: Foo
evil path '/tmp' will be added
I'm bar...
class loaded: Bar

с / tmp в пути к классам

java -cp .:/tmp EvilPathDemo
evil path '/tmp' in the classpath
I'm foo...
class loaded: Foo
I'm bar...
class loaded: Bar

удалить / tmp из пути к классам

java -cp .:/tmp EvilPathDemo remove
evil path '/tmp' in the classpath
evil path '/tmp' will be removed
java.lang.ClassNotFoundException: Foo
java.lang.ClassNotFoundException: Bar

Во время тестирования я обнаружил, что следующие случаи не работают.

  • addPath (evilPath);
    tryToLoad("Foo");
    removePath (evilPath); // не имел эффекта
    tryToLoad("Бар");
  • removePath (evilPath);
    tryToLoad("Foo");
    addPath (evilPath); // не имел никакого эффекта
    tryToLoad("Бар");
  • tryToLoad("Foo");
    removePath (evilPath); // не имел никакого эффекта
    tryToLoad("Бар");

Я не потратил время, чтобы выяснить, почему. Потому что я не вижу в этом практического применения. Если вам действительно нужно / хотите поиграть с classpath, посмотрите, как работают загрузчики классов.

removePath Метод сверху не работал для меня и моего сварочного контейнера, стек URL всегда был emtpy. Сработал следующий уродливый самодовольный метод:

public static void removeLastClasspathEntry() throws Exception {
    URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
    Class<?> urlClass = URLClassLoader.class;
    Field ucpField = urlClass.getDeclaredField("ucp");
    ucpField.setAccessible(true);
    URLClassPath ucp = (URLClassPath) ucpField.get(urlClassLoader);

    Field loadersField = URLClassPath.class.getDeclaredField("loaders");
    loadersField.setAccessible(true);
    List jarEntries = (List) loadersField.get(ucp);
    jarEntries.remove(jarEntries.size() - 1);

    Field pathField = URLClassPath.class.getDeclaredField("path");
    pathField.setAccessible(true);
    List pathList = (List) pathField.get(ucp);
    URL jarUrl = (URL) pathList.get(pathList.size() - 1);
    String jarName = jarUrl.toString();
    pathList.remove(pathList.size() - 1);

    Field lmapField = URLClassPath.class.getDeclaredField("lmap");
    lmapField.setAccessible(true);
    Map lmapMap = (Map) lmapField.get(ucp);
    lmapMap.remove(jarName.replaceFirst("file:/", "file:///"));
}

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

Сам системный загрузчик классов является неизменным (по уважительным причинам), но вы можете делать все, что захотите, во вложенных загрузчиках классов, включая уничтожение их для выгрузки классов и ресурсов. Это обычно используется, например, в ОСГИ и серверах приложений для загрузки / выгрузки, например, плагинов, приложений и т. Д.

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

У меня была та же проблема, поэтому я решил ее, создав библиотеку, которая работает на каждом ClassLoader который использует URLClassPath (так, в настоящее время, URLClassLoader).

В библиотеке есть методы для:

  • Добавление новых записей перед
  • Добавление новых записей
  • Удалить существующие записи

Обратите внимание, что, поскольку эта библиотека обращается к внутренним и проприетарным API, она не гарантированно будет работать в будущих версиях JDK. В настоящее время это делает для Java 7 и Java 8 (Oracle и OpenJDK).

Вот страница GitHub (вклад приветствуется), а вот артефакт Maven Central

Я не думаю, что есть прямой способ сделать это. Вы можете следить за:

  • Получите переменные пути класса, используя: System.getenv("CLASSPATH"), Он вернет значения, разделенные точкой с запятой.

    String classPath = System.getenv("CLASSPATH")

  • Возьмите путь к папке в качестве ввода и замените его на "", например:

    String remainigPath = classPath.replace(inputpath,"");

  • Поместите оставшиеся пути в массив, используя метод split.

    String[] paths = remainigPath .split(";");

  • Для добавления classPath, у вас уже есть код.

Другие вопросы по тегам