2013-12-24 5 views
7

Я должен динамически загружать класс с тем же именем из некоторого JAR, но различную реализацию, несколько раз.Можно ли «перезагрузить» загрузчик классов?

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

Тесты JUnit классы, экземпляры классов, которые должны быть проверены, это простой пример:

package evaluator.tests; 

import static org.junit.Assert.*; 

import org.junit.*; 

import evaluator.tested.*; 

public class KTest { 
    private K tested; 

    @Before 
    public void setup() { 
     tested = new K(); 
    } 

    @Test 
    public void returnsTrueTest() { 
     assertTrue(tested.returnsTrue()); 
    } 

} 

Остальная часть моего приложения нужно будет получить JAR-файлы от пользователей, которые будут содержать реализации функций K класс, который тестируется выше. Затем KTest должны были работать на классахK, а не в приложениях.

Я знаю, как динамически загружать класс, но я не знаю, как сделать с ним пробную работу, а не то, что я сделал.

Одно из решений, которые я разработал, состояло в том, чтобы изолировать тестирование в совершенно новом классе, например. Evaluation, создайте новый загрузчик классов в этом классе и загрузите все ссылочные классы. После создания загрузчика классов он загрузит класс K из файла JAR.

Это будет означать, что каждый раз, когда пользователь отправляет свой JAR, будет создан экземпляр отдельного Evaluation, он создаст свой собственный загрузчик классов и начнет тест JUnit. Когда это произойдет, тест будет использовать реализацию пользователя K, , а не по умолчанию.

Возможно ли это, и как это можно сделать?

Я читал, что загрузчики классов всегда спрашивают своих родителей, загружен ли класс. Это означало бы, что мне придется как-то «сбросить» все классы, которые я загрузил динамически из JAR-файла в Evaluation, чтобы они были выгружены, а затем снова загружены в другой Evaluation.

нагрузки класс K, тестовый класс K, выгрузить класс K, повторите с различными K.

ответ

1

Я сделал сообщение в блоге по этому вопросу, в котором проблематика объясняется немного дальше: http://yannbane.com/2014/01/manipulating-java-class-loading-mechanisms/.

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

Это, как я понимаю, URLClassLoaders (и загрузчики классов в целом) работы:

URLClassLoader.loadClass() получает автоматически вызывается, если это текущий загрузчик классов (для метода/класса исполняющего). Вызов сначала передается его родительскому загрузчику классов, и если ничего не найдено, он использует свой собственный findClass(), чтобы загрузить его с одного из URL-адресов, которые он имеет. loadClass() здесь (и логика загрузки) просто наследуется от обычного ClassLoader.

Поведение этого метода по умолчанию (loadClass()) заключается в том, чтобы сначала делегировать поиск родительскому объекту, и если родитель не может найти этот класс, только тогда его собственный findClass(). Этот метод (findClass()) по умолчанию (в) оставлен не реализованным, и вы должны его реализовать самостоятельно, по существу получая байт-коды класса где-нибудь (например, файл или сеть) и вызывая на них defineClass().

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

Все мои классы тестов будут пытаться getClass().getClassLoader().loadClass() всех классов, которые они ссылаются - в том числе мои пользовательские классы тестов (это регулярное поведение все классы, не только мои тесты, чтобы быть ясно). До тех пор, пока они используют стандартные классы и другие классы из моего приложения, которые не подлежат тестированию, этот метод загрузки классов должен быть дополнительно делегирован загрузчику классов приложений. Однако, как только они пытаются загрузить тестируемый класс, им необходимо получить собственную, специально загруженную пользовательскую версию.

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

Решение заключается в следующем:

  1. Реализовать основные версии чтобы быть тестируемых классов, так что тесты будут компилировать и работать без каких-либо представленных JAR. Это могло бы (скорее всего, должно) быть сделано, используя полиморфизм, но на данный момент не планируется (это означает, что пользователь, скорее всего, должен распространять «базовую» версию класса самостоятельно, локально, перед отправкой тестируемого класса).
  2. Расширьте URLClassLoader и переработайте логику классовой загрузки.
    • Выполняйте необходимые проверки (уже загруженный класс), которые существуют в реализации по умолчанию.
    • Попробуйте найтиКласс самостоятельно, если его нет в ваших URL-адресах, бросьте ClassNotFoundException.
    • Если класс уже загружен или был загружен только с некоторого URL-адреса, верните класс.
    • Если класс не был загружен до (этим загрузчиком классов) и не указан в одном из URL-адресов, делегируйте поиск родительскому объекту.
    • Если родитель возвращает класс, верните класс, если нет, бросьте ClassNotFoundException (он фактически будет выброшен родителем).
  3. Для каждого файла JAR с классом, который посылается:
    • Instantiate Обычай URLClassLoader
    • Добавьте JAR к нему и конкретный класс тест, а
    • Попросите его, чтобы загрузить тестовый класс , На этом этапе моя пользовательская логика загрузки классов запускается, и она загружает мой тест прямо с диска - заново, без делегирования его родительскому загрузчику классов. Зачем? Он называет defineClass() на моем тестовом классе, который устанавливает этот обычай URLClassLoader в качестве родителя тестового класса
    • Дайте тестовый класс в JUnit, который затем создает экземпляр его и начинает испытывать
  4. После того, как один из моих тестов работает , каждый раз, когда они ссылаются на любой класс, они будут называть свой собственный загрузчик классов loadClass(). Сначала они будут искать URL-адреса - поэтому, если они ссылаются на тестируемый класс, он будет загружен оттуда. В случае, если они ссылаются на какой-либо другой класс приложения или на системный класс, пользовательская логика загрузки классов просто делегирует этот вызов, и будет возвращен уже загруженный (предположительно) класс.

Как я уже сказал, я еще не реализовал это, и мне очень хотелось бы, если бы вы указали мои ошибки в комментариях.

ресурсы, из которых я собрал эту информацию:

  1. http://www.onjava.com/pub/a/onjava/2003/11/12/classloader.html
  2. http://www.devx.com/Java/Article/31614
  3. http://www.onjava.com/pub/a/onjava/2005/01/26/classloading.html
  4. http://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html#loadClass%28java.lang.String,%20boolean%29
3

Да, вы можете это сделать.

Если вы, например, используете java.net.URLClassLoader с нулевым родителем: new URLClassLoader(urlArray , null). Затем стартовый ClassLoader будет использоваться в качестве родителя для вашего ClassLoader.

Вот пример Класс, который просто использует новый загрузчик классов, перезагружает класс.

package com.anarsoft.agent.regression; 

import java.net.URL; 
import java.net.URLClassLoader; 

public class TestClassLoading { 


public static boolean field = false; 


public static void main(String[] args) throws Exception 
{ 


    URL[] urlArray = new URL[] { TestClassLoading.class.getProtectionDomain().getCodeSource().getLocation().toURI().toURL() }; 

    URLClassLoader firstClassloader = new URLClassLoader(urlArray , null); 

    Class firstClass = firstClassloader.loadClass("com.anarsoft.agent.regression.TestClassLoading"); 

    firstClass.getField("field").setBoolean(null,true); 

    System.out.println(firstClass.getField("field").getBoolean(null)); // true 


    URLClassLoader secondClassloader = new URLClassLoader(urlArray , null); 

    Class secondClass = secondClassloader.loadClass("com.anarsoft.agent.regression.TestClassLoading"); 



    System.out.println(secondClass.getField("field").getBoolean(null)); // false 
     // the static field is false since its a new loaded class 

} 

}

+0

И загрузчик классов не "запоминать" классы, загруженных или что? – corazza

+1

см. Wikipedia: загрузчик классов загрузки загружает основные библиотеки Java [5], расположенные в каталоге /jre/lib. Этот загрузчик классов, который является частью основного JVM, написан в собственном коде. –

+0

В основном загрузчик классов bootstrap загружает классы в пакеты java .... Я не думаю, что его можно перезагрузить эти классы, например java.lang.String или java.util.LinkedList –

0

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

+0

И пользовательский загрузчик классов будет «разгружать» эти классы, когда? Это важно, так как каждый тест должен быть изолирован. Загрузите класс 'K', тестовый класс' K', выгрузите класс 'K', повторите с разными' K'. – corazza

+0

@yannbane Попробуйте и скажите нам. – Darkhogg

0

Вы можете обмануть его.

  1. Кассы, который вы хотите сбросить, не должен быть в пути к классам
  2. Можно реализовать интерфейс или расширить класс, который находится в пути к классам (полезно для приведения типа или нужно использовать отражение для «открыть» и вызов методы, ИЛИ вызывающий класс вызывает другой метод, который имеет код, вызывает вызовы класса (ов) под тестом
  3. каждый раз, когда вы хотите протестировать, переименуйте банку, загрузите из банки, используя подкласс класса загрузчика класса. инициализация вашего нулевого загрузчика - это нормально.
  4. Итак, если вы сначала загрузите a1.jar, загрузите с a2.jar в следующий раз и убедитесь, что a1.jar перенесен в другую папку
Смежные вопросы