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

Фон:
У меня есть класс MainTest, в котором есть много кнопок, каждый из которых создает экземпляр класса, который я кодирую/тестирую. Я хочу, чтобы цикл кода/теста для этих классов был быстрым, и быстро наблюдайте эффект моих изменений несколько раз в минуту. MainTest, который является стабильным, занимает около 20 секунд для загрузки, что не было бы проблемой, если бы мне не нужно было перезагружать его для каждого изменения в классах, которые он создает. Я хочу загрузить MainTest один раз, и когда он создает экземпляр другого класса, позвоните ему ChildTest, много раз (при событии кнопки), он должен перезагрузить последнюю версию ChildTest. Короче говоря:
Как вы сообщаете команде "новая" java для перезагрузки класса с диска, а не из кеша jvm?


Я попробовал Class.ForName, но это не повлияло.
Я также попытался использовать пользовательский загрузчик классов (скопированный из открытого источника), но безрезультатно.

Ответ 1

Нет никакой надежды на "перегрузку" оператора new, но вы, безусловно, могли бы написать пользовательский загрузчик классов, который просто перезагружает байт-код каждый раз, когда вы попросите его загрузить класс. Никакие внеклассные загрузчики классов не будут делать то, что вы ищете, потому что все они предполагают, что определение класса не изменится в течение срока службы JVM.

Но вот как вы это делаете. Создайте загрузчик классов, называемый Reloader, который переопределяет методы loadClass и findClass, чтобы они просто перезагружали файлы классов с диска каждый раз, когда они вызываются (вместо "кэширования" их для последующего использования). Затем вам просто нужно вызвать new Reloader().loadClass("foo.bar.MyClassName") в любое время, когда вы подозреваете, что определение класса изменилось (например, как часть ваших жизненных циклов схемы тестирования).

Эта статья заполняет некоторые детали, но пропускает некоторые важные моменты, особенно об использовании новых экземпляров загрузчика классов для последующей перезагрузки и делегирования по умолчанию загрузчик классов. Вот простой рабочий пример, который неоднократно загружает класс MyClass и предполагает, что его файл класса существует в относительном каталоге "./bin":

public class Reloader extends ClassLoader {
    public static void main(String[] args) throws Exception {
        do {
            Object foo = new Reloader().loadClass("MyFoo").newInstance();
            System.out.println("LOADED: " + foo); // Overload MyFoo#toString() for effect
            System.out.println("Press <ENTER> when MyFoo.class has changed");
            System.in.read();
        } while (true);
    }

    @Override
    public Class<?> loadClass(String s) {
        return findClass(s);
    }

    @Override
    public Class<?> findClass(String s) {
        try {
            byte[] bytes = loadClassData(s);
            return defineClass(s, bytes, 0, bytes.length);
        } catch (IOException ioe) {
            try {
                return super.loadClass(s);
            } catch (ClassNotFoundException ignore) { }
            ioe.printStackTrace(System.out);
            return null;
        }
    }

    private byte[] loadClassData(String className) throws IOException {
        File f = new File("bin/" + className.replaceAll("\\.", "/") + ".class");
        int size = (int) f.length();
        byte buff[] = new byte[size];
        FileInputStream fis = new FileInputStream(f);
        DataInputStream dis = new DataInputStream(fis);
        dis.readFully(buff);
        dis.close();
        return buff;
    }
}

При каждом вызове блока "do/while" в основном методе создается экземпляр нового Reloader, который загружает класс с диска и возвращает его вызывающему. Поэтому, если вы перезапишете файл bin/MyClass.class, чтобы добавить новую реализацию с другим перегруженным методом toString, вы должны увидеть новую реализацию каждый раз.

Ответ 2

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

EDIT: Помимо использования API отладки, вы можете использовать Instrumentation. http://download-llnw.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html

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

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

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

Ответ 3

Звучит немного страшно, но это должно помочь.

http://download.oracle.com/javase/1.4.2/docs/api/java/lang/ClassLoader.html

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

Ответ 5

Вы можете попробовать Java Rebel. Это "только для разработки" класс-загрузчик, предназначенный для того, чтобы делать именно то, что вам нужно, но, может быть, оно может быть дорогостоящим для ваших нужд. В вашем случае решение Питера Лоури может быть достаточно.

Ответ 6

Я не уверен, как это сделать в коде, но у NetBeans есть кнопка "Применить изменения кода", которая сделает это.

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

Поскольку NetBeans может это сделать, должен быть способ, но я не знаю, как это сделать вручную.

Если вы запустите в среде IDE, которая поддерживает эту функцию, вы можете просто нажимать кнопку каждый раз, когда вы изменяете класс. Затем он перекомпилирует этот класс и перезагрузит его.