Иногда спасти ситуацию могут unit тесты. Но для кода связанного с представлением, написание тестов является занятием малоприятным.
В очередной раз мучаясь с отладкой приложения, решил исследовать вопрос изменения кода приложения без его перезапуска (согласитесь, возможность разработки в стиле Ruby on Rails, когда изменение исходного кода сразу же находит отражение в запущенном приложении, прекрасна). Но кое-что есть и в Java. Среди продуктов общего назначения стоит отметить JRebel и HotSwap. JRebel продукт коммерческий, HotSwap открытый. Я решил поиграть со вторым.
HotSwap использует расширения JPDA, появившиеся еще в версии Java 1.4. Они позволяют во время отладочной сессии менять код классов. Требования на возможные изменения достаточно жесткие. Можно менять только тела методов (попытка перегрузить класс с новым методом или с измененной сигнатурой метода приведет к UnsupportedOperationException).
Итак ближе к делу. Исходная точка: клиент и сервер. Клиент вызывает методы сервера.
public class Server { private String a() { return "'a value'"; } public String action() { return a(); } } public class Client { public static void main(String[] args)throws InterruptedException{ final Server server = new Server(); while (true) { System.out.println(server.action()); Thread.sleep(1000); } } }
Код собираем и запускаем ant скриптом:
<property name="classes" value="${basedir}/classes"/> <property name="src" value="${basedir}/src"/> <src path="${src}"/> <pathelement location="${classes}"/>
... <property name="port" value="5555"/> ...
...... <pathelement location="${classes}"/> <jvmarg value="-Xdebug"/> <jvmarg value="-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=${port}"/>
<property name="classes" value="${basedir}/classes"/> <property name="src" value="${basedir}/src"/> <property name="port" value="5555"/> <taskdef name="hotswap" classname="dak.ant.taskdefs.Hotswap"/> <format property="class.tstamp" pattern="MM/dd/yyyy kk:mm:ss"/> <src path="${src}"/> <date datetime="${class.tstamp}" pattern="MM/dd/yyyy kk:mm:ss" when="after" granularity="0"/> <src path="${src}"/> <pathelement location="${classes}"/> <jvmarg value="-Xdebug"/> <jvmarg value="-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=${port}"/>
private String a() { return "'new a value'"; }
Ant hotswap и voila наша программа стала печатать 'new a value'. Но не стоит ожидать от HotSwap магии. Например, рассмотрим такой код сервера:
public class Server { private String b; public void init() { b = "'b value'"; } private String b() { return b; } private String a() { return "'a value'"; } public String action() { return a() + " " + b(); } }
Мы запускаем приложение, меняем код инициализации:
public void init() { b = "'new b value'"; }
Выполняем горячую замену и ничего не происходит... Переменная b уже проинициализирована в момент старта, и наше изменение кода инициализации никак не влияет на уже установленное значение. Нам самим надо заботится о переинициализации наших компонентов. Например, так:
public final class Client { public static void main(String[] args)throws InterruptedException{ final Server server = new Server(); server.init(); final Scanner scanner = new Scanner(System.in); while (scanner.hasNextLine()) { final String command = scanner.nextLine(); if ("reinit".equals(command)) { server.init(); } System.out.println(server.action()); } } }
В этом случае, после изменения кода и горячей замены, посылаем сигнал reinit и получаем в выводе измененное значение.
В качестве заключения можно сказать, что HotSwap очень приятная библиотека (прежде всего простотой использования). Я использовал её в большом приложении со сложной системой сборки, и при этом внедрить HotSwap оказалось довольно легко. Но в силу ограниченных возможностей, использовать её можно только для отладки или совсем минимальной разработки.
Комментариев нет:
Отправить комментарий