Andreas Bruns

Softwareentwicklung für Oldenburg und Bremen

Neue JRebel Lizenz – weiterhin keine Redeployments mehr

Meine einjährige „JRebel Personal Lizenz“ (kostete 156 Dollar) läuft jetzt im Dezember ab und staunend musste ich feststellen, dass die Lizenz abgeschafft wurde und Zeroturnaround stattdessen die „JRebel Base Lizenz“ für 265 Dollar (+ 20% Steuern) anbietet. Eine Verlängerung der Lizenz wollte ich mir nach der erheblichen Preiserhöhung noch überlegen, aber als Besitzer einer „JRebel Personal Lizenz“ bekommt man von Zeroturnaround eine ermäßigte „JRebel Base Lizenz“ für 154 Dollar (+ 20% Steuern) – da musste ich natürlich gleich zuschlagen.

Wer JRebel nicht kennt, fragt sich sicherlich: „Warum gibt ein Java-Entwickler für Entwicklungswerkzeuge heutzutage noch Geld aus?“. Wir haben doch schon so viele gute kostenlose Frameworks, Werkzeuge und Servers: Spring, Eclipse, Git, Jenkins, Tomcat, JBoss usw.

JRebel: Vergleich mit JVM Hot Swap

JRebel: Vergleich mit JVM Hot Swap (Quelle: Zeroturnaround)

JRebel bietet nach der Vergleichsmatrix offenbar einige Features, für die Entwickler auch gerne bezahlen. Doch was bedeutet JRebel für den Alltag eines Java-Entwicklers?

Wenn eine Java-Anwendung läuft, werden Änderungen am Quellcode normalerweise nicht direkt in die laufende Anwendung übernommen. Wenn man jedoch die Anwendung im Debug-Modus startet, werden Quellcode-Änderungen dank der „JVM Hot Swap“-Technik direkt ausgeführt, solange die Änderungen nur innerhalb einer Methode erfolgen. Mit JRebel kann jedoch die Klassen-Struktur auf vielfätige Weise geändert werden und die Änderungen werden in dielaufende Anwendung übernommen.

Ein Playground-Beispiel

Ein schönes Start-Beispiel wird in diesem interessanten Blog-Artikel eines Zeroturnaround-Mitarbeiters gegeben, obwohl das Beispiel auch mit „JVM Hot Swap“-Technik funktionieren würde ;-). Bei meinem angepassten Beispiel stellt man nach ein bisschen rumspielen fest, dass viele Änderungen funktionieren, solange keine statischen Methoden oder Konstanten betroffen sind:

  • einen neuen Status „FOUR“ einführen
  • den Status auf „FOUR“ ändern
  • Klasse „MyStatus“ umbenennen zu „OurStatus“
  • Konstruktor „public OurStatus(Status status)“ entfernen
  • Standard-Konstruktor einführen und Status mit „ONE“ initialisieren
  • Status „ONE“ im Konstruktor zu Konstante „STATUS“ refaktorisieren und auf „TWO“ setzen
  • Wert der Konstante „STATUS“ auf „THREE“ ändern => funktioniert nicht
package de.bruns.example.jrebel;

import java.util.Arrays;

public class JRebelTestLoop {

    public static void main(String... args) throws Exception {
        while (true) {
            tellStatus();
            Thread.sleep(5000);
        }
    }

    private static void tellStatus() {
        MyStatus myStatus = new MyStatus(Status.TWO);
        System.out.println("Status: " + myStatus.getStatus() + " of " +  Arrays.asList(Status.values()));
    }

    private enum Status {
        ONE, TWO, THREE;
    }

    public static class MyStatus {

        private Status status;

        public MyStatus(Status status) {
            this.status = status;
        }

        public Status getStatus() {
            return status;
        }

        public void setStatus(Status status) {
            this.status = status;
        }

        @Override
        public String toString() {
            return MyStatus.class.getSimpleName() + ": " + status;
        }
    }
}

Das führt zu folgender Ausgabe:

/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/bin/java -Drebel.env.ide=intellij -Drebel.notification.url=http://localhost:54345 -javaagent:/var/folders/fn/3yh4478158zbwj0jd_dwk3m40000gn/T/jrebel.jar -Didea.launcher.port=7538 "-Didea.launcher.bin.path=/Applications/IntelliJ IDEA 12.app/bin" -Dfile.encoding=UTF-8 -classpath "/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/deploy.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/dt.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/javaws.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/jce.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/jconsole.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/management-agent.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/plugin.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/sa-jdi.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/charsets.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/classes.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/jsse.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/ui.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/ext/apple_provider.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/ext/dnsns.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/ext/localedata.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/ext/sunjce_provider.jar:/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/ext/sunpkcs11.jar:/Volumes/Daten/Projekte/blog-examples/example-jrebel/target/classes:/Users/bruno/.m2/repository/org/springframework/spring-webmvc/3.0.7.RELEASE/spring-webmvc-3.0.7.RELEASE.jar:/Users/bruno/.m2/repository/org/springframework/spring-asm/3.0.7.RELEASE/spring-asm-3.0.7.RELEASE.jar:/Users/bruno/.m2/repository/org/springframework/spring-beans/3.0.7.RELEASE/spring-beans-3.0.7.RELEASE.jar:/Users/bruno/.m2/repository/org/springframework/spring-core/3.0.7.RELEASE/spring-core-3.0.7.RELEASE.jar:/Users/bruno/.m2/repository/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar:/Users/bruno/.m2/repository/org/springframework/spring-context/3.0.7.RELEASE/spring-context-3.0.7.RELEASE.jar:/Users/bruno/.m2/repository/org/springframework/spring-aop/3.0.7.RELEASE/spring-aop-3.0.7.RELEASE.jar:/Users/bruno/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/Users/bruno/.m2/repository/org/springframework/spring-expression/3.0.7.RELEASE/spring-expression-3.0.7.RELEASE.jar:/Users/bruno/.m2/repository/org/springframework/spring-context-support/3.0.7.RELEASE/spring-context-support-3.0.7.RELEASE.jar:/Users/bruno/.m2/repository/org/springframework/spring-web/3.0.7.RELEASE/spring-web-3.0.7.RELEASE.jar:/Users/bruno/.m2/repository/org/freemarker/freemarker/2.3.16/freemarker-2.3.16.jar:/Users/bruno/.m2/repository/org/apache/velocity/velocity/1.7/velocity-1.7.jar:/Users/bruno/.m2/repository/commons-collections/commons-collections/3.2.1/commons-collections-3.2.1.jar:/Users/bruno/.m2/repository/commons-lang/commons-lang/2.4/commons-lang-2.4.jar:/Users/bruno/.m2/repository/javax/servlet/jstl/1.2/jstl-1.2.jar:/Users/bruno/.m2/repository/de/neuland/spring-jade4j/0.2.3/spring-jade4j-0.2.3.jar:/Users/bruno/.m2/repository/de/neuland/jade4j/0.2.14/jade4j-0.2.14.jar:/Users/bruno/.m2/repository/ognl/ognl/3.0.3/ognl-3.0.3.jar:/Users/bruno/.m2/repository/javassist/javassist/3.11.0.GA/javassist-3.11.0.GA.jar:/Users/bruno/.m2/repository/commons-io/commons-io/2.1/commons-io-2.1.jar:/Users/bruno/.m2/repository/org/apache/commons/commons-lang3/3.1/commons-lang3-3.1.jar:/Users/bruno/.m2/repository/com/google/code/gson/gson/2.1/gson-2.1.jar:/Users/bruno/.m2/repository/org/slf4j/slf4j-api/1.6.0/slf4j-api-1.6.0.jar:/Users/bruno/.m2/repository/org/pegdown/pegdown/1.1.0/pegdown-1.1.0.jar:/Users/bruno/.m2/repository/org/parboiled/parboiled-core/1.0.2/parboiled-core-1.0.2.jar:/Users/bruno/.m2/repository/org/parboiled/parboiled-java/1.0.2/parboiled-java-1.0.2.jar:/Users/bruno/.m2/repository/asm/asm/3.3.1/asm-3.3.1.jar:/Users/bruno/.m2/repository/asm/asm-util/3.3.1/asm-util-3.3.1.jar:/Users/bruno/.m2/repository/asm/asm-tree/3.3.1/asm-tree-3.3.1.jar:/Users/bruno/.m2/repository/asm/asm-analysis/3.3.1/asm-analysis-3.3.1.jar:/Applications/IntelliJ IDEA 12.app/lib/idea_rt.jar" com.intellij.rt.execution.application.AppMain de.bruns.example.jrebel.JRebelTestLoop
2013-11-23 23:20:16 JRebel: 
2013-11-23 23:20:16 JRebel: #############################################################
2013-11-23 23:20:16 JRebel: 
2013-11-23 23:20:16 JRebel:  JRebel 5.4.1 (201310171404)
2013-11-23 23:20:16 JRebel:  (c) Copyright ZeroTurnaround OU, Estonia, Tartu.
2013-11-23 23:20:16 JRebel: 
2013-11-23 23:20:16 JRebel:  Over the last 30 days JRebel prevented 
2013-11-23 23:20:16 JRebel:  at least 83 redeploys/restarts saving you about 3.4 hours.
......
2013-11-23 23:20:16 JRebel:     http://sales.zeroturnaround.com
2013-11-23 23:20:16 JRebel: 
2013-11-23 23:20:16 JRebel:  The following plugins are disabled at the moment: 
2013-11-23 23:20:16 JRebel:  * Axis2 plugin (set -Drebel.axis2_plugin=true to enable)
2013-11-23 23:20:16 JRebel:  * Camel plugin (set -Drebel.camel_plugin=true to enable)
2013-11-23 23:20:16 JRebel:  * Click plugin (set -Drebel.click_plugin=true to enable)
2013-11-23 23:20:16 JRebel:  * Deltaspike plugin (set -Drebel.deltaspike_plugin=true to enable)
2013-11-23 23:20:16 JRebel:  * Eclipse RCP Plugin (set -Drebel.eclipse_plugin=true to enable)
2013-11-23 23:20:16 JRebel:  * JRuby Plugin (set -Drebel.jruby_plugin=true to enable)
2013-11-23 23:20:16 JRebel:  * Jersey plugin (set -Drebel.jersey_plugin=true to enable)
2013-11-23 23:20:16 JRebel:  * Log4j2 plugin (set -Drebel.log4j2_plugin=true to enable)
2013-11-23 23:20:16 JRebel:  * Mustache Plugin (set -Drebel.mustache_plugin=true to enable)
2013-11-23 23:20:16 JRebel:  * RESTlet plugin (set -Drebel.restlet_plugin=true to enable)
2013-11-23 23:20:16 JRebel:  * Seam-Wicket plugin (set -Drebel.seam_wicket_plugin=true to enable)
2013-11-23 23:20:16 JRebel:  * Spring Data Plugin (set -Drebel.spring_data_plugin=true to enable)
2013-11-23 23:20:16 JRebel:  * Thymeleaf Plugin (set -Drebel.thymeleaf_plugin=true to enable)
2013-11-23 23:20:16 JRebel:  * VRaptor plugin (set -Drebel.vraptor_plugin=true to enable)
2013-11-23 23:20:16 JRebel:  * Vaadin CDI utils plugin (set -Drebel.vaadin_cdiutils_plugin=true to enable)
2013-11-23 23:20:16 JRebel:  * WebObjects plugin (set -Drebel.webobjects_plugin=true to enable)
2013-11-23 23:20:16 JRebel: 
2013-11-23 23:20:16 JRebel: #############################################################
2013-11-23 23:20:16 JRebel: 
2013-11-23 23:20:16 JRebel: Directory '/Volumes/Daten/Projekte/blog-examples/example-jrebel/target/classes' will be monitored for changes.
2013-11-23 23:20:16 JRebel: Directory '/Volumes/Daten/Projekte/blog-examples/example-jrebel/src/main/webapp' will be monitored for changes.
MyStatus: TWO of [ONE, TWO, THREE]
MyStatus: TWO of [ONE, TWO, THREE]
2013-11-23 23:20:31 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop'.
2013-11-23 23:20:31 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop$Status'.
2013-11-23 23:20:31 JRebel: Reinitialized class 'de.bruns.example.jrebel.JRebelTestLoop$Status'.
2013-11-23 23:20:32 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop$MyStatus'.
MyStatus: TWO of [ONE, TWO, THREE, FOUR]
2013-11-23 23:20:57 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop'.
2013-11-23 23:20:57 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop$Status'.
2013-11-23 23:20:57 JRebel: Reinitialized class 'de.bruns.example.jrebel.JRebelTestLoop$Status'.
2013-11-23 23:20:57 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop$MyStatus'.
MyStatus: FOUR of [ONE, TWO, THREE, FOUR]
2013-11-23 23:22:17 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop'.
2013-11-23 23:22:17 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop$Status'.
2013-11-23 23:22:17 JRebel: Reinitialized class 'de.bruns.example.jrebel.JRebelTestLoop$Status'.
OurStatus: FOUR of [ONE, TWO, THREE, FOUR]
2013-11-23 23:23:52 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop'.
2013-11-23 23:23:52 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop$OurStatus'.
2013-11-23 23:23:52 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop$Status'.
2013-11-23 23:23:52 JRebel: Reinitialized class 'de.bruns.example.jrebel.JRebelTestLoop$Status'.
OurStatus: null of [ONE, TWO, THREE, FOUR]
2013-11-23 23:25:42 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop'.
2013-11-23 23:25:42 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop$OurStatus'.
2013-11-23 23:25:42 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop$Status'.
2013-11-23 23:25:42 JRebel: Reinitialized class 'de.bruns.example.jrebel.JRebelTestLoop$Status'.
OurStatus: ONE of [ONE, TWO, THREE, FOUR]
2013-11-23 23:27:22 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop'.
2013-11-23 23:27:22 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop$OurStatus'.
2013-11-23 23:27:22 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop$Status'.
2013-11-23 23:27:22 JRebel: Reinitialized class 'de.bruns.example.jrebel.JRebelTestLoop$Status'.
2013-11-23 23:27:22 JRebel: Reinitialized class 'de.bruns.example.jrebel.JRebelTestLoop$OurStatus'.
OurStatus: TWO of [ONE, TWO, THREE, FOUR]
2013-11-23 23:27:22 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop'.
2013-11-23 23:27:22 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop$OurStatus'.
2013-11-23 23:27:22 JRebel: Reloading class 'de.bruns.example.jrebel.JRebelTestLoop$Status'.
2013-11-23 23:27:22 JRebel: Reinitialized class 'de.bruns.example.jrebel.JRebelTestLoop$Status'.
2013-11-23 23:27:22 JRebel: Reinitialized class 'de.bruns.example.jrebel.JRebelTestLoop$OurStatus'.
OurStatus: TWO of [ONE, TWO, THREE, FOUR]

IDE-, Container- und Technologie-Unterstützung

JRebel bietet für die wichtigsten IDEs Plugins an, mit denen die JRebel-Funktionen sehr einfach genutzt werden können. So wird automatisch die Konfigurationsdatei „rebel.xml“ angelegt, die beispielsweise die zu überwachenden Programmcode-Verzeichnisse angibt. Ich habe folgende Konstellationen mit einem Spring-Beispiel aus einem vorherigen Blog-Artikel ausgeführt:

  • Eclipse mit Tomcat per Sysdeo-Plugin und Spring-Webanwendung
  • Eclipse mit Jetty per RunJettyRun-Plugin und Spring-Webanwendung
  • IntelliJ IDEA mit Tomcat und Spring-Webanwendung

JRebel funktioniert wunderbar und es werden nicht nur die Klassen neu geladen, sondern auch neue Spring-Controller erkannt und in die laufende Spring-Konfiguration übernommen. Einige Tutorials mit den unterstützten IDEs, Containers- und Technologien gibt es hier. Falls trotzdem bei einer Startklasse JRebel nicht eingebunden wird, helfen eventuell diese VM-Argumente:

-Drebel.log=true ${jrebel_args}

IntelliJ IDEA mit JRebel-Plugin im Einsatz

Der folgende Screenshot zeigt das JRebel-Plugin für IntelliJ IDEA im Einsatz. Innerhalb der Entwicklungsumgebung IntelliJ IDEA wird ein Tomcat-Server mit einer Webanwendung gestartet. Dabei wird sogar ein weiterer Spring-Controller in das laufende System übernommen:

  • Webanwendung (z.B. diese Spring-Anwendung) ist für Deployment im Tomcat-Server konfiguriert
  • in der IDE haben wir uns dank des JRebel-Plugins die Datei rebel.xml generiert
  • Datei rebel.xml sorgt für Überwachung folgender Verzeichnisse:
    – Verzeichnis mit der Webanwendung-Konfiguration
    – Verzeichnis mit Class-Dateien der Webanwendung
  • wir starten die Anwendung mit der JRebel-Konfiguration
  • wir erstellen eine neue Spring-Controller-Klasse
  • die Spring-Controller-Klasse wird von JRebel geladen
  • JRebel erkennt die Klasse als Spring-Bean und erweitert die Spring-Konfiguration
JRebel-Plugin in IntelliJ IDEA

JRebel-Plugin in IntelliJ IDEA

Fazit – Geld sparen durch Geld ausgeben

Wenn man die JRebel-Technologie kennengelernt hat, fühlt sich das Entwickeln ohne JRebel wirklich umständlich an. Andererseits birgt der Einsatz von JRebel die Gefahr, schnell mal eben für ein Feature oder Bugfix anhand des laufenden Systems solange rumzuschrauben, bis alles passt – Tests stören da nur.

Ob nämlich folgende beispielhafte Berechnung sinnvoll ist (die von JRebel ähnlich gemacht wird), hängt von der Vorgehensweise des Entwicklers ab:

Neustart einer Webanwendung im Tomcat = 30 Sekunden
Anzahl der Neustarts am Tag = 20 mal
Einsparungen pro Tag dank JRebel = 600 Sekunden = 10 Minuten
Einsparungen pro Monat dank JRebel = 10 Minuten * 20 Tage = 200 Minuten = 3h 20m

Falls ein Entwickler direkt anhand des laufenden Systems entwickelt, mag solch eine Berechnung passen. Wenn ein Entwickler allerdings nach TDD (Test Driven Development) entwickelt, nutzt er vorwiegend die geschriebenen Tests und nicht das laufende System zur Überprüfung seines Programms. Die Berechnung ist jedenfalls gut, um die Kosten für JRebel vom Chef genehmigt zu bekommen (z.B. mit diesen Präsentationen). Ansonsten lohnt sich ein Blick auf die kostenlosen Alternativen in diesem Blog-Artikel: JRebel Like Free Alternative Software. Viel Spaß beim Coden mit „Zero Turnaround“ Zeiten :-).

Kommentare sind geschlossen.