Перезагрузка используемых классов во время выполнения Java

Я работаю над программой, которая следит за каталогом и запускает все тесты в каталоге, когда видит изменения в каталоге.

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

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

Мой код для динамической загрузки классов и возврата списка тестовых классов:

List<Class<?>> classes = new ArrayList<Class<?>>();
    for (File file : classFiles) {
        String fullName = file.getPath();
        String name = fullName.substring(fullName.indexOf("bin")+4)
                .replace('/', '.')
                .replace('\\', '.'); 
        name = name.substring(0, name.length() - 6);

            tempClass = new DynamicClassLoader(Thread.currentThread().getContextClassLoader()).findClass(name)          } catch (ClassNotFoundException e1) {
            // TODO Decide how to handle exception
            e1.printStackTrace();
        }

        boolean cHasTestMethods = false;
        for(Method method: tempClass.getMethods()){
            if(method.isAnnotationPresent(Test.class)){
                cHasTestMethods = true;
                break;
            }
        }
        if (!Modifier.isAbstract(cachedClass.getModifiers()) && cHasTestMethods) {
            classes.add(tempClass);
        }
    }
    return classes;

с DynamicClassLoader в качестве описанного здесь Reloader Как заставить Java перезагружать класс при создании экземпляра?

Есть идея, как это исправить? Я думал, что все классы будут динамически загружены. Обратите внимание, однако, что я не перезаписываю loadclass в своем DynamicClassLoader, потому что, если мои тестовые классы дают init

EDIT: Это не работает, класс загружается, но тесты в нем не обнаружены...

List<Request> requests = new ArrayList<Request>();
    for (File file : classFiles) {
        String fullName = file.getPath();
        String name = fullName.substring(fullName.indexOf("bin")+4)
                .replace('/', '.')
                .replace('\\', '.'); 
        name = name.substring(0, name.length() - 6);
        Class<?> cachedClass = null;
        Class<?> dynamicClass = null;
        try {
            cachedClass = Class.forName(name);


            URL[] urls={ cachedClass.getProtectionDomain().getCodeSource().getLocation() };
            ClassLoader delegateParent = cachedClass .getClassLoader().getParent();
            URLClassLoader cl = new URLClassLoader(urls, delegateParent) ;
            dynamicClass = cl.loadClass(name);
            System.out.println(dynamicClass);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

Редактировать редактирование: я обнаруживаю методы тестирования следующим образом:

            for(Method method: dynamicClass.getMethods()){
            if(method.isAnnotationPresent(Test.class)){
                requests.add(Request.method(dynamicClass, method.getName()));
            }
        }

Ответ 1

Если вы использовали пользовательский ClassLoader точно так же, как в связанном ответе, он не отменяет метод protected Class<?> loadClass(String name, boolean resolve). Это означает, что, когда JVM разрешает зависимости, он все равно будет делегировать загрузчик родительского класса. И, конечно же, когда он не делегировал родительскому элементу ClassLoader, он мог потерять некоторые требуемые классы.

Самое простое решение - установить правильный загрузчик родительского класса. Вы в настоящее время проходите Thread.currentThread().getContextClassLoader(), что немного странно, поскольку ваше главное намерение состоит в том, что делегирование не должно делегироваться этому загрузчику, а загружать измененные классы. Вы должны думать о том, какие классные загрузчики существуют и которые использовать, а какие нет. Например. если класс Foo находится в пределах вашего текущего кода, но вы хотите (повторно) загрузить его с помощью нового ClassLoader, Foo.class.getClassLoader().getParent() будет правильным родителем-делегатом для нового ClassLoader. Обратите внимание, что это может быть null, но это не имеет значения, поскольку в этом случае он будет использовать загрузчик начальной загрузки, который является правильным родителем.

Обратите внимание, что при настройке правильного родителя ClassLoader, соответствующего вашим намерениям, вам больше не понадобится этот пользовательский ClassLoader. Реализация по умолчанию (см. URLClassLoader) уже делает правильные вещи. И с текущими версиями Java это Closeable делает его еще более подходящим для сценариев динамической загрузки.

Вот простой пример перезагрузки класса:

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;

public class ReloadMyClass
{
  public static void main(String[] args)
  throws ClassNotFoundException, IOException {
    Class<?> myClass=ReloadMyClass.class;
    System.out.printf("my class is [email protected]%x%n", myClass.hashCode());
    System.out.println("reloading");
    URL[] urls={ myClass.getProtectionDomain().getCodeSource().getLocation() };
    ClassLoader delegateParent = myClass.getClassLoader().getParent();
    try(URLClassLoader cl=new URLClassLoader(urls, delegateParent)) {
      Class<?> reloaded=cl.loadClass(myClass.getName());
      System.out.printf("reloaded my class: [email protected]%x%n", reloaded.hashCode());
      System.out.println("Different classes: "+(myClass!=reloaded));
    }
  }
}