Добавить или удалить определенные банки из пользовательского ClassLoader

Я хочу создать динамически загрузчик классов для выполнения сценария JSR223 в контролируемая среда, но терпящая неудачу,

Я пытаюсь удалить/добавить банки, используя текущий (родительский) ClassLoader, я попробовал решение Динамическое удаление банок из путь к классам

public class DistributionClassLoader extends ClassLoader {
    public DistributionClassLoader(ClassLoader parent) {
        super(parent);
    }
    private Map<String, ClassLoader> classLoadersByDistribution =
            Collections.synchronizedMap(new WeakHashMap<>());
    private final AtomicReference<String> distribution = new AtomicReference<>();
    @Override
    protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException {
        final ClassLoader delegate = classLoadersByDistribution.get(distribution.get());
        if (delegate != null) return Class.forName(name, true, delegate);
        throw new ClassNotFoundException(name);
    }
    public void addDistribution(String key, ClassLoader distributionClassLoader){
        classLoadersByDistribution.put(key,distributionClassLoader);
    }
    public void makeDistributionActive(String key){distribution.set(key);}
    public void removeDistribution(String key){
        final ClassLoader toRemove = classLoadersByDistribution.remove(key);
    }
}

Но в него не вошли все мои баночки, в тесте эта работа

ClassLoader cl = this.getClass().getClassLoader();
Class cls = cl.loadClass("org.springframework.http.HttpStatus");

Но использование решения не находит класс

ClassLoader cl = new DistributionClassLoader(this.getClass().getClassLoader());
Class cls = cl.loadClass("org.springframework.http.HttpStatus");

Исключение:

java.lang.ClassNotFoundException: org.springframework.http.HttpStatus
    at com.DistributionClassLoader.loadClass(DistributionClassLoader.java:24)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)

Как выбрать конкретные банки для добавления или удаления из ClassLoader?

ИЗМЕНИТЬ

Я могу загружать банки с помощью @czdepski answer но я все еще хочу удалить все/большинство классов, кроме JDK

Method sysMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
sysMethod.setAccessible(true);
sysMethod.invoke(sysLoader, new Object[]{url});

person user7294900    schedule 10.07.2019    source источник
comment
Можете ли вы объяснить, почему именно вы хотите удалить вещи из загрузчика классов? поскольку это не совсем возможно, вы не можете принудительно выгрузить класс. Классы могут быть собраны мусором с помощью загрузчика классов. Пожалуйста, приведите несколько примеров, когда вам нужно что-то подобное.   -  person GotoFinal    schedule 06.09.2019
comment
@gotofinal см. bz.apache.org/bugzilla/show_bug.cgi?id=63538   -  person user7294900    schedule 06.09.2019
comment
Я не понимаю, как это связано с удалением jar-файлов из загрузчика классов, при выполнении groovy-скрипта просто нужно загрузить его с помощью другого загрузчика классов, который не использует эту библиотеку. Поэтому такие библиотеки не должны быть частью загрузчика родительского класса. Поскольку удаление их из загрузчика родительского класса все равно ничего не изменит.   -  person GotoFinal    schedule 06.09.2019
comment
@gotofinal как создать загрузчик классов на основе существующего загрузчика классов?   -  person user7294900    schedule 06.09.2019
comment
проверьте, не похоже ли то, что я написал в ответе, на то, что вам нужно.   -  person GotoFinal    schedule 06.09.2019


Ответы (1)


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

Если мы посмотрим на файл Javadoc для ClassLoader.loadClass(String,boolean) находим:

Загружает класс с указанным двоичным именем. Реализация этого метода по умолчанию ищет классы в следующем порядке:

  1. Вызовите findLoadedClass(String), чтобы проверить, был ли уже загружен класс.

  2. Вызовите метод loadClass в загрузчике родительского класса. Если родитель имеет значение null, вместо этого используется загрузчик классов, встроенный в виртуальную машину.

  3. Вызовите метод findClass(String), чтобы найти класс.

Если класс был найден с помощью описанных выше шагов и флаг разрешения имеет значение true, этот метод вызовет метод resolveClass(Class) для полученного объекта класса.

Подклассам ClassLoader рекомендуется переопределять findClass(String), а не этот метод.

Вы переопределили loadClass, но не делегировали его родительскому ClassLoader.
Вместо этого вы вызываете classLoadersByDistribution.get(distribution.get());, что, скорее всего, null (трудно сказать, но всегда ожидайте, что WeakHashMap.get() вернет null).

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

В конце концов, это звучит как проблема XY. Вы хотите выполнить некоторый код с помощью API сценариев и каким-то образом управлять средой.
Вы пытались использовать SecurityManager?


О вашем комментарии о том, что вам нужен собственный ClassLoader для создания ScriptEngineManager: этот ClassLoader используется для поиска реализаций ScriptEngineFactory. Это делается с помощью интерфейс поставщика услуг.
Если вы не используете свой собственный скриптовый движок, это не должно иметь для вас значения.


Если ваша цель — добавить несколько jar-файлов, чтобы движок мог их использовать, создайте новый URLClassLoader с загрузчиком класса платформы в качестве родителя. (Или загрузчик класса расширения, зависит от версии Java.)
Установите этот ClassLoader как Thread.setContextClassLoader() и создайте ScriptEngine.

Если вы правильно выбрали родителя URLClassLoader, он не увидит классы, загружаемые загрузчиком классов приложения.

person Johannes Kuhn    schedule 05.09.2019
comment
Что касается последнего комментария, мне нужен ClassLoader, см. docs.oracle.com/javase/7/docs/api/javax/script/ - person user7294900; 05.09.2019
comment
Итак, вы хотите предоставить свой собственный ScriptEngine? Загружались через собственный ClassLoader? - person Johannes Kuhn; 05.09.2019
comment
Я хочу обновить (добавить/удалить банки) с помощью текущего загрузчика классов перед отправкой в ​​ScriptEngine - person user7294900; 05.09.2019
comment
Вы не должны изменять ClassLoader после его создания. Это опасно. (См. JVMS о загрузке классов.) - person Johannes Kuhn; 05.09.2019
comment
но я хочу использовать более безопасный/настраиваемый загрузчик классов на основе существующего загрузчика классов - person user7294900; 05.09.2019
comment
Более безопасный, чем URLClassLoader? Удачи с этим. Нет, правда, чего ты хочешь? - person Johannes Kuhn; 05.09.2019
comment
Да, смотрите мой последний абзац. Не реализуйте свой собственный ClassLoader. Это сложно сделать, поэтому просто используйте URLClassLoader в качестве загрузчика класса контекста при создании движка. - person Johannes Kuhn; 05.09.2019
comment
Но тогда мне не удалось добавить банки, см. stackoverflow.com/questions/56864832/ - person user7294900; 05.09.2019
comment
Используйте правильный родитель при создании URLClassLoader. В Java 7 это может означать использование null в качестве родителя. Что может исключать классы, загруженные через загрузчик классов расширения. - person Johannes Kuhn; 05.09.2019