Hintergrund#
Wiederholtes Deployment kostet eine Menge Zeit im Entwicklungsprozess und stört den Flow. Daher evaluierte ich den Einsatz von JavaRebel des Herstellers Zero Turnaround.Zusammenfassung#
Grundsätzlich funktioniert das angepriesene Hot Replacement von Klassen und Ressourcen für einfache Projekte. Innerhalb eines realistischen Projekts in der aktuellen Zeit, finden eine Menge Zugriffe auf Resourcen über verschiedene Mechanismen geladen. Alleine die Lademechanismen der Application Server finden nur zum Teil über ClassLoader statt, zum anderen auch über URLs vom Typ http, file oder gar jar. Hinzu kommen Frameworks wie Spring, Seam, Facelets etc. Weite Lademechanismen kommen oft durch spezielle Projektanforderungen hinzu.Für die meisten der Application Server und einige der Frameworks bietet Zero Turnaround auch Plugins an, sodass diese vollständiger unterstützt werden oder zum Teil überhaupt noch korrekt arbeiten. Ich habe ca. 12 Stunden investiert, ein Projekt mit Maven, Glassfish, Seam, Facelets und einigen Facelet-Libraries unter Rebel zum Laufen zu bekommen. Nach einiger Recherchearbeit war ich mir nicht im Klaren, wie die vom Hersteller empfohlene Vorgehensweise wäre und ob meine Anforderungen damit überhaupt erfüllbar wären.
Bericht#
Die Anleitung beschreibt zunächst sogar, welche Parameter der Java-VM übergeben werden sollen: "-Xverify:none -javaagent:javarebel.jar" sei ausreichend. Dann wunderte ich mich über den fehlenden Hinweis auf die Platzierung der jar-Datei. Also probierte ich /glassfish/lib, /glassfish/bin, /glassfish/domains/domain1/lib etc. aus, mit jeweiligen Hoch- und Runterfahren des Application Servers. Als ich keinen Erfolg hatte, setzte ich einen absoluten Pfad ein. Schließlich sah ich im Logfile eine Meldung des JavaRebel, dass ich nun 30 Tage zur Evaluierung hätte.Ich konnte mir schwer vorstellen, dass nun plötzlich jegliche Ressourcen automatisch geladen werden könnten, da JavaRebel die Pfade nicht wissen kann. Eine weitere Recherche brachte vielfältige Möglichkeiten zu Tage, vor allem dass eine rebel.xml Datei zur Konfiguration gebraucht würde. Und es sei uncool, darin absolute Pfade zu benutzen. Am Anfang hat man ja immer noch den Anspruch alles entsprechend den Vorgaben des Herstellers durch zu führen, um hinterher nicht der Kritik ausgesetzt zu sein "Warum sind Sie das nicht entsprechend der Anleitung verfahren?" Daher verwendete ich das Maven Plugin für JavaRebel, das mir auch prompt die rebel.xml Dateien in alls jar und war Dateien rein generierte.
Nach einem Redeploy flog mir zunächst Seam um die Ohren, mit seiner typischen Meldung "Two components with same name and precedence". Nach etwas Trial and Error debugge ich also in Seam rein und stelle fest, dass er die components.xml aus jeder jar Datei zweimal verarbeitet. Als URLs identifizierte ich Verlinkungen auf den Maven Output Folder target/classes sowie auf src/main/resources. Also sah ich mir die rebel.xml an und fand dort auch zwei Classpath Einträge auf die entsprechenden Verzeichnisse. Da Seam den Klassenpfad scannt, wunderte mich das Ergebnis auch nicht mehr.
Also versuchte ich das Maven Plugin zu überreden nur den target/classes Pfad zu verwenden. Das ging zunächst nur sehr zäh vorwärts, da Zero Turnaround sehr zurückhaltend mit der Veröffentlichung des Codes umgeht. Dann fand ich wenigstens den Code des Maven Plugins unter http://repos.zeroturnaround.com/svn/. Nach eingehender Studie von org.zeroturnaround.javarebel.maven.GenerateRebelMojo stellte ich fest, dass es nicht möglich war Classpath Einträge weg zu nehmen.
Dann machte mir die ersten Gedanken wie "Pass ich das Plugin an? Nein, dann bin ich von Updates abgehängt.", "Entwickle ich selbst ein Plugin? Nein, zunächst könnte ich die rebel.xml selbst testen." Also entfernte ich das Maven Plugin wieder. Die Betrachtung der rebel.xml brachte zu Tage, dass dort absolute Pfade verwendet wurden, sehr hässlich. Dessen war sich offenbar auch der Hersteller bewusst, da auf einer Seite des für mich unübersichtlich erscheinenden Webs vermerkt war, dass es besser wäre relative Pfade zu benutzen. "../.." wären wohl bei jar und war korrekt, dazu müsse man aber noch im Maven Plugin einen Eintrag über einen absoluten Pfad machen, den man wiederum in eine System Variable auslagert. Ich wusste nun nicht, auf was sich dieses "../.." nun genau bezieht, außerdem funktionierte das Maven Plugin für mein Projekt nicht.
Als Resultat entschied ich mich, die rebel.xml Datei selbst zu generieren. Diese ist auch nicht besonders kompliziert. Ich nutzte das Filtering des Maven Resource Plugin, um die absoluten Pfade durch ${basedir} etc. zu ersetzen, was auch auf Anhieb klappte. Dann ging ich aufs Ganze und trug die Konfiguration in das root pom.xml ein und fügte in jede jar und war passende rebel.xml Templates ein. Alles neu Durchgeneriert, krachte es wieder beim Deployment. Seam konnte zwar korrekt hochfahren, aber es konnten Facelets nicht geladen werden.
Im Debugger sah man leider nicht viel, es kamen dieselben Pfade über den Facelets-Loader com.sun.facelets.impl.DefaultResourceResolver rein. Ich habe sogar in URL.openConnection() bis hinunter zur Implementierung von JarURLConnection etc. debugged und konnte keinen Unterschied zum standard Java Loading entdecken. Mein Schluss daraus, JavaRebel macht wohl irgendwelche Magic über die java.lang.instrument.Instrumentation API, die ja die Grundlage dieses Tools ist.
Ich wollte noch immer nicht aufgeben, daher checkte ich den Code der anderen Plugins von JavaRebel aus. Den Kern-Code konnte ich nicht finden, wahrscheinlich weil dieser kommerziell ist und einen Lizenzmechanismus enthält, der nach 30 Tagen ausläuft. Also war ich ziemlich hilflos um nachzusehen, woher JavaRebel genau die Ressourcen läd um da eine Korrektur vorzunehmen. Reverse Engineering wollte ich mir nicht antun, zumal das meines Wissens nicht mal erlaubt war. Aber besonders schön ist der Code meist auch nicht, den die entsprechenden Tools generieren. Außerdem könnte ich mir vorstellen, dass obendrein noch Obfuscation im Spiel sein könnte.
Entmutigt experimentierte ich noch etwas mit dem Eintragen verscheidener Pfade herum, indem ich den Code der Plugins studierte. Den Gedanken ein Plugin zu entwickeln schlug ich mir auch wieder aus dem Kopf. Diese waren zum Teil zwar sehr klein und einfach, allerdings bezogen diese sich auch auf Schnittstellen des JavaRebel Kerns von dem ich keinen Code hatte. Nach einem ganz großen und sinnlosen Turnaround und Zero Erfolg, strecke ich also die Waffen nieder ;-)
Fazit#
Für die Zeit so ein Projekt mit JavaRebel aufzusetzen, selbst wenn es erfolgreich ist, kann man viele, viele Redeploys machen. Langfristig wäre das schon eine interessante Sache, daher bin ich auch bereit das Thema irgendwann nochmals anzugreifen. Nur in ein Tool von einem kommerziellen Hersteller würde ich nicht mehr viel Zeit investieren, da durch den Mangel an Source Code sehr schwer abzuschätzen ist wie weit man vom Erfolg noch entfernt ist, ob es überhaupt möglich ist und an die Informationen heran zu kommen, die für den erfolgreichen Einsatz überhaupt möglich wären.Vision#
Da das Thema schon wichtig ist, langfristig ein kritischer Faktor für den Geschäftserfolg ist, spiele ich auch mit dem Gedanken selbst so einen Hot Deployer zu implementieren. Eigentlich ist es keine große Sache, über die Instrumentation API einen Wrapper um die Klassen zu bauen, der über einen Timestamp den Bedarf zur Aktualisierung prüft. Die xml Config Datei ist grundsätzlich auch eine gute Idee, um flexibel auf Anforderungen im Einsatz reagieren zu können.Dazu würde ich noch Prüfen, in wie weit man in den URL Lademechanismus zB. durch URL.setURLStreamHandlerFactory eingreifen kann. Damit könnte man relativ einfach Umleitungen für alle Arten von Resource-Loader definieren. Einzig das Caching der Frameworks wird ein Problem sein, dass nur durch individuelle Plugins in den Griff bekommen werden kann. Für 90% der Aufgaben sind keine Plugins erforderlich. Die Haupthindernisse sind Code-Changes und die Änderung von Ressourcen wie XHTML-Dateien.