Песочница →
JRebel
На Хабре несколько раз публиковались статьи, где JRebel либо просто упоминался, либо выкладывалась информация, что вышла новая версия. При этом, не всем читателям было понятно, о чём вообще речь, и как данное ПО работает.
Как непосредственному участнику разработки данного продукта, мне хотелось бы прояснить некоторые моменты, почему JRebel существует и как он может помочь Java-разработчику.
Изначальная проблема известна практически любому разработчику, который работает с Java: после каких-либо изменений в проекте, для того, чтобы увидеть результат, тратится довольно много времени на сборку и развёртывание в контейнере. На Хабре уже публиковались отличные статьи о том, как можно ускорить или автоматизировать процесс разработки, не стану повторяться. Но дело в том, что в упомянутых способах есть свои изъяны: далеко не все изменения возможно перегрузить в развёрнутом приложении штатными средствами; очень легко получить утечки памяти, которые приведут к надобности перезапуска контейнера. Технические детали хорошо расписаны в серии статей в нашем сайте — любопытных приглашаю почитать.
Как выглядит цикл разработки web-приложения, в классическом виде:
1. Сделали изменения в коде (или в ресурсах)
2. Собрали JAR/WAR/EAR
3. Развернули полученный архив в контейнере
4. Открыли развёрнутое приложение, и, после некоторых манипуляций увидели результаты своего труда.
В зависимости от размера приложения, используемого контейнера, и некоторых других факторов, этапы 2, 3 и 4 могут занимать от нескольких секунд, до совершенно невминяемых цифр. Наша компания проводила опрос разработчиков относительно используемых технологий и времени которое затрачивается на развёртывание приложения. Как оказалось, в среднем на развёртывание тратится около 3 минут за раз, и около 10 минут в час. В плачевных случаях, где на развёртывание уходит более полу часа, нет даже смысла спрашивать у человека, сколько раз в час он может повторить этот процесс. Ответ очевиден.
Когда перезапуск контейнера/приложения занимает считанные секунды, проблема, описанная выше, не ощущается так сильно. Однако, по мере роста и усложнения проекта, неудобства дадут о себе знать. Тут то и можно задуматься — может быть 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 и место нахождения статических ресурсов:
Как видите, при помощи такого файла, возможно отобразить любую, самую нестандартную структуру проекта — на моём опыте, такие требования возникают довольно часто. Детальное описание опций для 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.
Далее, предположим мы используем аннотации для настроек в рамках какого-либо фреймворка. Например, значение
Отработает это следующим образом: после компиляции, значение аннотации будет доступно в новой версии класса. Аннотации — это не исполняемый код, а некоторые мета-данные, в зависимости от которых меняет своё поведение сам фреймворк. Теперь, когда новая версия класса будет подгружена, нужно дать знать фреймворку знать, что мета-данные поменялись и стоит обновить своё поведение. Для таких случаев в JRebel необходима специальная интеграция для каждого фреймворка или контейнера.
Так же как и в случае с аннотациями, специальная интеграция требуется в случае если фреймворк опирается на внешнюю конфигурацию. Так например в случае со Spring Framework конфигурации могут задаваться как через аннотации так и через XML файлы.
К примеру, добавили новый компонент (bean) в XML конфигурации, и при помощи
Как и у всех подходов описанных на Хабре и у JRebel есть некоторые технические ограничения.
На момент написания этой статьи не поддерживается изменения иерархии классов, т.е. если в программе один класс уже описан как “A extends B” то изменить его на “A extends C” нельзя. Тоже самое относится и к изменению списка интерфейсов — нельзя добавить или удалить интерфейсы из объявления класса.
Существуют ещё некоторые моменты, которые стоит учитывать.
Статическая инициализация. JRebel старается сохранить состояние объектов, которые уже созданы в памяти. Соответственно, не происходит перезапуска конструкторов или статического блока инициализации. Из этого вытекает парочка последствий.
Изменив значение статического поля мы ожидаем увидеть это новое значение в новой версии класса. Значение это, на самом деле, будет присвоено в статическом блоке, который JRebel не перезапустил. Соответственно, нового значения мы не увидим. В данный момент JRebel перезапустит статический блок только в случае добавления нового статического поля.
Причина такого поведения в том, что происходящее в статическом блоке может неопределённым способом влиять на состояние объекта, поэтому JRebel старается перезапускать его только в самом крайнем случае.
Второе следствие, которое выходит из того, что JRebel не перезапускает конструкторы, это то, что при добавлении нового поля в класс будет присвоено значение “по умолчанию” для данного типа. Т.е. если добавить поле, тип которого не будет примитивом, то присвоенное значение будет null, что в общем случае может повлечь за собой NullPointerException в случае если это поле будет разыменовано для существующего объекта.
Как было описано выше, для поддержки конфигураций фреймворков в JRebel требуется специальная интеграция. Мы потратили уже довольно много сил и времени для реализации всевозможных интегараций, и список поддерживаемых фреймворков довольно внушителен. При работе с некоторыми фреймворками, случается, что интеграция и не требуется и JRebel работает с ними довольно неплохо “из коробки”. За примерами ходить далеко не надо: не имея специальной интеграции для Vaadin, JRebel работает с ним довольно хорошо, и наши финские коллеги очень довольны тем, что они могут использовать JRebel с собственным фреймворком.
Таких примеров, к сожалению, не так много. Большая часть фреймворков всё таки опирается на внешнюю конфигурацию, для которой требуется дополнительная интеграция. Мы бы рады реализовать поддержку всех и вся, но успеть за всеми неизвесными фреймворками мы не можем, да и во многих фирмах создаются свои «велосипеды», кода которых мы никогда не увидим. Для таких случаев в JRebel есть возможность писать свои интеграции. У нас на сайте есть небольшое руководство о том, как можно реализовать поддержку JRebel для своих нужд.
Для тех, кто заинтересовался и хочет узнать больше о продукте, и как его использовать, могу привести ссылки на некоторые ресурсы.
Создатель JavaPassion ведёт страничку, где собирает и обновляет материалы по использованию JRebel с разными контейнерами, фреймворками и IDE.
Довольно часто мы проводим тематические вебинары, на которые можно бесплатно зарегистрироваться и участвовать — в прямом эфире задать вопросы на интересующую тему.
На Vimeo существует канал 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 и как его можно использовать. Если возникнут вопросы, буду рад ответить на них в комментариях.
Для тех, кто уже пользуется, и находит какие то изъяны в поведении данного ПО, не стесняйтесь написать нам на форум или обратиться в службу техподдержки, будем очень признательны.
Спасибо за внимание!
02.01.2012 22:53+0400