пятница, 4 мая 2012 г.

HotSwap

Разработка и отладка приложений с большим объемом кода утомительна длинным циклом сборки. Плюс в зависимости от специфики сервиса, время на поднятие с нуля также может быть значительным.

Иногда спасти ситуацию могут 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}"/>
            
        
    


Ant run и через каждую минуту печатается 'a value'. Хотим менять поведение без перезапуска приложения. Для этого, прежде всего, надо запустить приложение в режиме отладки:
    ...
    <property name="port" value="5555"/>
    ...
        ...
        
            
                <pathelement location="${classes}"/>
            
            <jvmarg value="-Xdebug"/>
            <jvmarg value="-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=${port}"/>
        
        ...  
Шаг второй, определяем задачу для горячей замены. Скачиваем с сайта HotSwap библиотеку hotswap.jar и подкладываем её к ant'у. Теперь можно добавить таск. Полностью скрипт будет выглядеть следующим образом:




    <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}"/>
        
    


Запускаем приложение также как и раньше. Теперь изменим код метода a():
    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 оказалось довольно легко. Но в силу ограниченных возможностей, использовать её можно только для отладки или совсем минимальной разработки.

Комментариев нет:

Отправить комментарий