Перезагрузите используемые классы во время выполнения 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

РЕДАКТИРОВАТЬ: Это не работает, класс загружается, но тесты в нем не обнаружены...

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 Class@%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: Class@%x%n", reloaded.hashCode());
      System.out.println("Different classes: "+(myClass!=reloaded));
    }
  }
}
Другие вопросы по тегам