Осенью прошлого года на конференции JavaOne в Сан-Франциско, традиционно были присуждены награды лучшим проектам года "2011 Duke's Choice Awards". И хотелось бы подготовить цикл небольших статей, коротко описывающих каждый из проектов-победителей.
Начнем данный цикл с номинации "Innovative Compiler for Java Code", награду в которой получил проект JRebel, эстонской компании ZeroTurnaround, созданный Евгением Кабановым (Jevgeni Kabanov) и Томасом Румером (Toomas Römer).
Стоит отметить, что это не единственная их награда за прошедший год, этот проект был признан "Самой инновационной технологией Java" на JAX Innovation Awards. А в числе пользователей JRebel значатся IBM, HP, AT&T, Bank of America, Disney, LinkedIn, ... Для того, чтобы понять предназначение JRebel начнем чуть-чуть издалека Рассмотрим два простых класса. Первый класс имеет лишь переопределенный метод toString():
Если же во время выполнения программы мы изменим строку "TestModule, version 1!" на "TestModule, version 2!", и применяя раздельную компиляцию заново скомпилируем класс TestModule, то от этого значение выводимой строки не измениться.
Это происходит потому, что стандартный (системный) ClassLoader при первом же обращении к классу, а точнее при вызове его конструктора (либо при обращении к статическому методу или полю), динамически загружает и кеширует байт-код класса в недра JVM, и в дальнейшем уже не обращается к файловой системе. Можете даже удалить файл TestModule.class, это никак не отразиться на работе программы.
Перечень загружаемых во время работы программы классов, и директории откуда они загружаются, можно узнать подставив при запуске программы ключик java -verbose:class .... Узнаете много чего интересного ;)
Из приведенного выше примера следует, то, что после изменения исходного кода (и, соответственно, байт-кода) необходимо перезапускать все приложение. А в случае промышленного (Java Web, Java EE) приложения заново разворачивать его на сервере. Вообщем-то логично.
Все бы ничего, но проведенные исследования показали, что разработчики тратят в среднем от 10 до 13 минут на час программирования, на периодическое развертывания приложений.
Чтобы сократить потери времени, необходимо "научить" JVM обновлять байт-код классов в случаи их изменений, без необходимости перезапуска приложения.
В простом случае для этого достаточно написать свой загрузчик классов, который наследуется от абстрактного класса ClassLoader, и переопределить в нем ряд методов таким образом, чтобы при каждом создании экземпляра класса его байт-код считывался с файловой системы. Это на самом деле делается не так страшно, как может показаться и подробно, вместе с примерами, описывается в статье "ClassLoader – скрытые возможности".
Экземпляр собственного ClassLoader-а предается в качестве параметра статического метода Class.forName(String name, boolean initialize, ClassLoader loader), после чего с помощью рефлексии создается экземпляр требуемого класса:
В данном примере DynamicClassOverloader (код по ссылке) как раз и является нашим загрузчиком классов (подробности в указанной статье "ClassLoader – скрытые возможности").
Можно проверить, что теперь во время работы программы при изменении строки "TestModule, version 1!" на "TestModule, version 2!" будет меняться и выводимое в консоль сообщение.
Все было бы замечательно, если бы не ряд особенностей. Первая из которых начнется если откомментировать 10 строчку. В результате получим замечательное RuntimeException:
Почему оно происходит? Запустим нашу программу с ключиком java -verbose:class Test, и обратим внимание на следующий фрагмент:
В первой строке происходит загрузка класса TestModule с помощью нашего собственного загрузчика классов, во второй - вывод сообщения, в третей - стандартный (системный) загрузчик классов снова загружает класс TestModule, именно в тот момент когда пытаемся осуществить приведение типов:
Получается, что класс к которому мы хотим привести (преобразовать) объект загружен с помощью системного загрузчика, а класс на основании которого этот объект object был создан загружен с помощью нашего собственного загрузчика. Хоть классы одинаковы (и их байт-код идентичен), но загружены разными загрузчиками, и, как следствием, JVM считает их абсолютно разными. Такую вот особенность, и далеко не единственную, которую нужно учитывать при написании собственного загрузчика классов. Теперь, что же такое JRebel JRebel - это плагин к JVM, который позволяет на лету перезагружать классы и другие ресурсы, которые были изменены с момента развёртывания приложения. При этом он не имеет тех проблем и особенностей, которые возникают при написании собственного загрузчика классов. При загрузке класса в JVM, JRebel отслеживает соответствующий ему .class-файл и в случае его изменения подгружает изменившийся класс через расширенный загрузчик классов. При этом старые экземпляры классов и сам класс сохраняется, что позволяет приложению продолжать работу без потери данных. Новые же экземпляры класса будут создаются уже на основании обновленного байт-кода класса. На данный момент JRebel поддерживает практически все изменения, которые могут осуществляться в исходном коде (JRebel Features):
P.S. Если кто-то захочет написать небольшую статью о других проектах получивших награды "2011 Duke's Choice Awards", не стесняйтесь ;)
За помощь в подготовке данного материа спасибо Полянскому Дмитрию
Стоит отметить, что это не единственная их награда за прошедший год, этот проект был признан "Самой инновационной технологией Java" на JAX Innovation Awards. А в числе пользователей JRebel значатся IBM, HP, AT&T, Bank of America, Disney, LinkedIn, ... Для того, чтобы понять предназначение JRebel начнем чуть-чуть издалека Рассмотрим два простых класса. Первый класс имеет лишь переопределенный метод toString():
public
class
TestModule {
@Override
public
String toString() {
return
"TestModule, version 1!"
;
}
}
Второй класс в бесконечном цикле создает экземпляр объекта первого класса и выводит его на экран:
public class Test { public static void main(String[] argv) { for (;;) { TestModule t = new TestModule(); System.out.println(t); System.console().readLine(); } } } |
public
class
Test {
public
static
void
main(String[] argv)
throws
Exception {
for
(;;) {
ClassLoader loader =
new
DynamicClassOverloader(
new
String[] {
"."
});
// текущий каталог "." будет единственным каталогом поиска для
// класса "TestModule"
Class clazz = Class.forName(
"TestModule"
,
true
,loader);
Object object = clazz.newInstance();
System.out.println(object);
//TestModule test = (TestModule) object;
System.console().readLine();
}
}
}
Exception in thread "main" java.lang.ClassCastException: TestModule cannot be cast to TestModule
Почему оно происходит? Запустим нашу программу с ключиком java -verbose:class Test, и обратим внимание на следующий фрагмент:
...
[Loaded TestModule from __JVM_DefineClass__]
TestModule, version 1!
[Loaded TestModule from file:/C:/Projects/DynamicClassLoader/build/classes/]
...
В первой строке происходит загрузка класса TestModule с помощью нашего собственного загрузчика классов, во второй - вывод сообщения, в третей - стандартный (системный) загрузчик классов снова загружает класс TestModule, именно в тот момент когда пытаемся осуществить приведение типов:
TestModule test = (TestModule) object;
Получается, что класс к которому мы хотим привести (преобразовать) объект загружен с помощью системного загрузчика, а класс на основании которого этот объект object был создан загружен с помощью нашего собственного загрузчика. Хоть классы одинаковы (и их байт-код идентичен), но загружены разными загрузчиками, и, как следствием, JVM считает их абсолютно разными. Такую вот особенность, и далеко не единственную, которую нужно учитывать при написании собственного загрузчика классов. Теперь, что же такое JRebel JRebel - это плагин к JVM, который позволяет на лету перезагружать классы и другие ресурсы, которые были изменены с момента развёртывания приложения. При этом он не имеет тех проблем и особенностей, которые возникают при написании собственного загрузчика классов. При загрузке класса в JVM, JRebel отслеживает соответствующий ему .class-файл и в случае его изменения подгружает изменившийся класс через расширенный загрузчик классов. При этом старые экземпляры классов и сам класс сохраняется, что позволяет приложению продолжать работу без потери данных. Новые же экземпляры класса будут создаются уже на основании обновленного байт-кода класса. На данный момент JRebel поддерживает практически все изменения, которые могут осуществляться в исходном коде (JRebel Features):
- Changes to method bodies
- Adding/removing Methods
- Adding/removing constructors
- Adding/removing fields
- Adding/removing classes
- Adding/removing annotations
- Changing static field value
- Adding/removing enum values
- Changing interfaces
- Replacing superclass
- Adding/removing implemented interfaces
- Отчет - "Java EE Productivity Report 2011"
- Видео с конференции и набор статей от автора JRebel, Евгения Кабанова - Reloading Java Classes: Technical Series
- Статья - "5 JRebel Features You Couldn’t Do In The JVM"
P.S. Если кто-то захочет написать небольшую статью о других проектах получивших награды "2011 Duke's Choice Awards", не стесняйтесь ;)
За помощь в подготовке данного материа спасибо Полянскому Дмитрию
Комментариев нет:
Отправить комментарий