Яндекс.Метрика

    Песочница

    JRebel

    На Хабре несколько раз публиковались статьи, где JRebel либо просто упоминался, либо выкладывалась информация, что вышла новая версия. При этом, не всем читателям было понятно, о чём вообще речь, и как данное ПО работает.

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

    Откуда ноги растут?



    Изначальная проблема известна практически любому разработчику, который работает с Java: после каких-либо изменений в проекте, для того, чтобы увидеть результат, тратится довольно много времени на сборку и развёртывание в контейнере. На Хабре уже публиковались отличные статьи о том, как можно ускорить или автоматизировать процесс разработки, не стану повторяться. Но дело в том, что в упомянутых способах есть свои изъяны: далеко не все изменения возможно перегрузить в развёрнутом приложении штатными средствами; очень легко получить утечки памяти, которые приведут к надобности перезапуска контейнера. Технические детали хорошо расписаны в серии статей в нашем сайте — любопытных приглашаю почитать.

    Куда уходит время?


    Как выглядит цикл разработки web-приложения, в классическом виде:
    1. Сделали изменения в коде (или в ресурсах)
    2. Собрали JAR/WAR/EAR
    3. Развернули полученный архив в контейнере
    4. Открыли развёрнутое приложение, и, после некоторых манипуляций увидели результаты своего труда.

    В зависимости от размера приложения, используемого контейнера, и некоторых других факторов, этапы 2, 3 и 4 могут занимать от нескольких секунд, до совершенно невминяемых цифр. Наша компания проводила опрос разработчиков относительно используемых технологий и времени которое затрачивается на развёртывание приложения. Как оказалось, в среднем на развёртывание тратится около 3 минут за раз, и около 10 минут в час. В плачевных случаях, где на развёртывание уходит более полу часа, нет даже смысла спрашивать у человека, сколько раз в час он может повторить этот процесс. Ответ очевиден.

    Когда перезапуск контейнера/приложения занимает считанные секунды, проблема, описанная выше, не ощущается так сильно. Однако, по мере роста и усложнения проекта, неудобства дадут о себе знать. Тут то и можно задуматься — может быть JRebel, это то что вам нужно?

    JRebel в помощь!



    Итак, JRebel — это инструмент, призванный избавить от проблемы повторного развёртывания приложения во время разработки, а то есть может сэкономить вам много времени.

    Какие приемущества появляются при использовании JRebel:
    1. Поскольку JRebel умеет загружать ресурсы прямо из рабочего пространства, то отпадает надобность собирать полный архив приложения (JAR/WAR/EAR). Остаётся только скомпилировать изменённый код, что занимает гораздо меньше времени, чем полная сборка архива.
    2. Не происходит повторного развёртывания приложения — снова экономим время.
    3. Не создаются новые загрузчики классов (classloaders), поэтому меньше риск получить утечки памяти при обновлениях. Соответственно, экономим время на вынужденных перезапусках самого контейнера.
    4. Сохраняется состояние объектов и пользовательской сессии. Поэтому, в идеале, можно оставаясь на одной и той же странице, видеть результаты изменений — лишь жми F5.

    Установка и настройка


    Есть несколько вариантов установки. Т.к. большинство разработчиков всё таки работают используя IDE, такие как Eclipse, NetBeans или IntelliJ, то и естественным способом установки является установка плагина для отдельно взятого IDE.

    Пользователи Eclipse, для установки, могут воспользоваться сервисом Eclipse Marketplace. Пользователи NetBeans и IntelliJIDEA могут найти JRebel в соответствующем списке плагинов. Детальные инструкции можно найти здесь. После установки плагина вам дадут знать, что неплохо бы зарегистрироваться и получить лицензию



    После регистрации JRebel готов к употреблению.

    На самом деле, JRebel не привязан к конкретной среде разработки, т.к. работает он не в IDE, а там, где запущено приложение — т.е. привязан к JVM процессу при помощи -javaagent аргумента, примерно так:

    java -javaagent:/opt/jrebel/jrebel.jar -cp. my.awesome.Application

    Конфигурация


    В большинстве случаев JRebel не требует дополнительной настройки, лишь конфигурационный файл — rebel.xml — который может быть сгенерирован автоматически при помощи IDE-плагина.



    Суть в том, что агент (javaagent), коим и является JRebel, должен знать, где находятся скомпилированные классы, и статические файлы (html, css, итд). Это позволит загружать все требуемые ресурсы не из развёрнутого архива, а прямо из проекта, где программист и вносит свои изменения.

    Если бы все проекты следовали бы “стандартной” структуре каталогов, и компилировались бы только штатными средствами IDE, то возможно, конфигурационный файл был бы и не нужен — мы могли бы получать всю информацию о проекте из IDE или работать просто по конвенции. Но поскольку у всех разработчиков своё представление о стандартах, то и JRebel нуждается в некоторых подсказках.

    Сам конфигурационный файл имеет довольно простую структуру — нужно всего лишь задать classpath и место нахождения статических ресурсов:

    <?xml version="1.0" encoding="UTF-8"?>
    <application
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns="http://www.zeroturnaround.com"
      xsi:schemaLocation="http://www.zeroturnaround.com http://www.zeroturnaround.com/alderaan/rebel-2_0.xsd">
     <classpath>
        <dir name="c:\myWorkspace\myWar\target\classes"/>
        <dir name="c:\myWorkspace\myWar\src\main\resources"/>
     </classpath>
      <web>
        <link target="/">
          <dir name="c:\myWorkspace\myWar\src\main\webapp"/>
       </link>
        <link target="/jsps/">
         <dir name="c:\myWorkspace\myWar\src\main\jsps"/>
       </link>
      </web>
    </application>
    



    Как видите, при помощи такого файла, возможно отобразить любую, самую нестандартную структуру проекта — на моём опыте, такие требования возникают довольно часто. Детальное описание опций для rebel.xml можно найти в документации.

    Важно! Для того чтобы JRebel мог правильно зачитать конфигурацию, rebel.xml должен быть приложен к развёртываемому приложению: для web-приложений, rebel.xml должен оказаться в WEB-INF/classes. Для JAR файлов rebel.xml должен оказаться в корне архива.



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

    В случае Maven проектов, есть возможность использовать плагин, который умеет генерировать конфигурационный файл, и сохраняет его в правильное место.

    Запуск


    Из IDE запуск достаточно тривиален. В случае с IntelliJIDEA достаточно воспользоваться новой кнопкой запуска, которая появилась после установки плагина:



    В Eclipse, в конфигурации запуска будет добавлена новая закладка, на которой можно отметить включен ли будет JRebel для этого запуска, и некоторые другие опции.



    В случае, если хочется запускать контейнер вне среды разработки, то достаточно добавить правильный путь к jrebel.jar в параметре -javaagent в скриптах для избранного контейнера. В руководстве по конфигурации приведён список контейнеров с примерами скриптов для запуска для разных JVM и ОС.

    Возможности



    Кроме отображения проекта в развёрнутом приложении, что является просто удобным средством автоматизации, JRebel предлагает ещё некоторую функциональность. Прежде всего — это основная функциональность — перегрузка изменений в коде. Допустим по ходу работы наткнулись на метод, который вот руки как чешутся порефакторить. Сказано-сделано, вызываем extract method, что приводит к добавлению нового метода в класс. Стандартное средство для перегрузки кода, HotSwap, с таким изменением справиться не может. JRebel, в свою очередь, класс перегрузит и напишет об этом в консоль. Перегрузка состоится в тот момент, когда изменённый класс будет использован, тем самым симулируется ленивое поведение которое присуще Java.

    Далее, предположим мы используем аннотации для настроек в рамках какого-либо фреймворка. Например, значение @RequestMapping используется для определения пути по которому будет доступен контроллер. Поменяв значение аннотации, мы ожидаем обнаружить ресурс по новому пути в нашем приложении.
    Отработает это следующим образом: после компиляции, значение аннотации будет доступно в новой версии класса. Аннотации — это не исполняемый код, а некоторые мета-данные, в зависимости от которых меняет своё поведение сам фреймворк. Теперь, когда новая версия класса будет подгружена, нужно дать знать фреймворку знать, что мета-данные поменялись и стоит обновить своё поведение. Для таких случаев в JRebel необходима специальная интеграция для каждого фреймворка или контейнера.

    Так же как и в случае с аннотациями, специальная интеграция требуется в случае если фреймворк опирается на внешнюю конфигурацию. Так например в случае со Spring Framework конфигурации могут задаваться как через аннотации так и через XML файлы.
    К примеру, добавили новый компонент (bean) в XML конфигурации, и при помощи @Autowired аннотации хотим передать новоиспечённый компонент в контроллер.

    Ограничения


    Как и у всех подходов описанных на Хабре и у JRebel есть некоторые технические ограничения.

    На момент написания этой статьи не поддерживается изменения иерархии классов, т.е. если в программе один класс уже описан как “A extends B” то изменить его на “A extends C” нельзя. Тоже самое относится и к изменению списка интерфейсов — нельзя добавить или удалить интерфейсы из объявления класса.

    Существуют ещё некоторые моменты, которые стоит учитывать.

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

    Изменив значение статического поля мы ожидаем увидеть это новое значение в новой версии класса. Значение это, на самом деле, будет присвоено в статическом блоке, который JRebel не перезапустил. Соответственно, нового значения мы не увидим. В данный момент JRebel перезапустит статический блок только в случае добавления нового статического поля.

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

    Второе следствие, которое выходит из того, что JRebel не перезапускает конструкторы, это то, что при добавлении нового поля в класс будет присвоено значение “по умолчанию” для данного типа. Т.е. если добавить поле, тип которого не будет примитивом, то присвоенное значение будет null, что в общем случае может повлечь за собой NullPointerException в случае если это поле будет разыменовано для существующего объекта.

    JRebel SDK



    Как было описано выше, для поддержки конфигураций фреймворков в JRebel требуется специальная интеграция. Мы потратили уже довольно много сил и времени для реализации всевозможных интегараций, и список поддерживаемых фреймворков довольно внушителен. При работе с некоторыми фреймворками, случается, что интеграция и не требуется и JRebel работает с ними довольно неплохо “из коробки”. За примерами ходить далеко не надо: не имея специальной интеграции для Vaadin, JRebel работает с ним довольно хорошо, и наши финские коллеги очень довольны тем, что они могут использовать JRebel с собственным фреймворком.
    Таких примеров, к сожалению, не так много. Большая часть фреймворков всё таки опирается на внешнюю конфигурацию, для которой требуется дополнительная интеграция. Мы бы рады реализовать поддержку всех и вся, но успеть за всеми неизвесными фреймворками мы не можем, да и во многих фирмах создаются свои «велосипеды», кода которых мы никогда не увидим. Для таких случаев в JRebel есть возможность писать свои интеграции. У нас на сайте есть небольшое руководство о том, как можно реализовать поддержку JRebel для своих нужд.

    Ресурсы



    Для тех, кто заинтересовался и хочет узнать больше о продукте, и как его использовать, могу привести ссылки на некоторые ресурсы.
    Создатель JavaPassion ведёт страничку, где собирает и обновляет материалы по использованию JRebel с разными контейнерами, фреймворками и IDE.
    Довольно часто мы проводим тематические вебинары, на которые можно бесплатно зарегистрироваться и участвовать — в прямом эфире задать вопросы на интересующую тему.
    На Vimeo существует канал JRebel, где можно найти записи вебинаров, а также и всевозможные демо-записи.

    Итого



    Надеюсь, после данной статьи, непосвящённым читателям стало понятнее, что такое JRebel и как его можно использовать. Если возникнут вопросы, буду рад ответить на них в комментариях.
    Для тех, кто уже пользуется, и находит какие то изъяны в поведении данного ПО, не стесняйтесь написать нам на форум или обратиться в службу техподдержки, будем очень признательны.
    Спасибо за внимание!