Accueil > Non classé > Oubliez les redéploiements grâce à JRebel

Oubliez les redéploiements grâce à JRebel

Introduction

Comme une grande partie des lecteurs de ce blog, vous avez certainement déjà travaillé sur des applications web Java EE de taille variable, sur lesquelles vous avez fait un nombre indécent de redéploiements à chaque fois que vous vouliez valider des modifications fraîchement apportées. Puis vous avez dû découvrir que depuis Java 1.4, la JVM permet de faire du Hotswap en mode debug, c’est-à-dire de remplacer du code à la volée au runtime.

C’est bien, mais il y a quand même des limitations : on ne peut que modifier le corps d’une méthode déjà existante, si l’on ajoute une méthode ou une classe elle ne sera pas prise en compte. Idem pour les membres statiques, les annotation ou encore les fichiers properties accédés via un ResourceBundle.

C’est pour pallier ces manques que la société Estonienne ZeroTurnaround a développé JRebel, un outil destiné aux développeurs désirant gagner du temps dans leur cycle de développement.

JRebel, c’est quoi ?

Comme l’explique en anglais un article du blog de ZeroTurnaround, JRebel est principalement un agent à installer dans votre serveur d’applications, qui va réagir à des mises à jour de classes, ou plus généralement de ressources composant votre application. À l’aide d’un fichier de configuration rebel.xml, vous allez spécifier une liste de ressources à surveiller, par exemple des répertoires contenant des JAR et des fichiers properties. Lors du déploiement de votre application, l’agent va utiliser cette configuration pour scruter des changements dans ces répertoires (un timestamp plus récent).

Pour appliquer ces changements, JRebel introduit un classloader qui va se charger de détecter le chargement des classes, pour ensuite générer des classes anonymes grâce la génération de bytecode on-the-fly. À chaque modification d’une classe dans votre IDE, une nouvelle classe anonyme sera générée pour modifier le comportement de la classe originale, grâce à un savant mélange de JIT et de magie noire (l’article ne rentre pas trop dans les détails du fonctionnement interne, secret de fabrication oblige puisque c’est un outil commercial).

Fonctionnement de l'agent (image issue du site de ZeroTurnaround)

Au final, la perte de performances est minimisée comparé au gain de temps entrainé par le fait que l’on ne soit pas obligé de redéployer toutes les 5 minutes. Que vous utilisiez des JAR, des WAR, du Spring, Struts, Wicket ou autre Hibernate, sur des serveurs Glassfish, Websphere, JBoss, ou encore Weblogic, il y a de fortes chances que JRebel le supporte de base ou via des plugins. Et si votre framework favori n’est pas dans la liste, une API vous permet de développer votre propre plugin.

Mise en pratique

Cas simple : modification d’une méthode

Nous allons maintenant tester JRebel sur une application web type HelloWorld. Comme je l’ai déjà dit plus haut, c’est un outil commercial mais une version d’évaluation de 30 jours est disponible sur le site. L’installation est très bien expliquée par un wizard prenant en compte votre IDE et votre serveur, donc je ne m’attarderai pas sur le sujet.

J’ai choisi d’utiliser Netbeans et Glassfish pour déployer une application toute simple : une calculatrice. Je dispose d’une servlet nommée Calculator, chargée de faire des additions et des soustractions :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Calculator extends HttpServlet {

    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");

        String operation = request.getParameter("operation");
        Integer result = null;

        if (operation != null && request.getParameter("a") != null && request.getParameter("b") != null) {
            Integer a = Integer.valueOf(request.getParameter("a"));
            Integer b = Integer.valueOf(request.getParameter("b"));

            if (operation.equals("add")) {
                result = a + b;
            } else if (operation.equals("sub")) {
                result = a - b;
            }
        } else {
            throw new IllegalArgumentException("Don't fool me, use correct parameters: a, b and operation!!");
        }

        PrintWriter out = response.getWriter();

        try {
            out.println("The result is " + ((result == null) ? "unpredictable" : result));
        } finally {
            out.close();
        }
    }

Attention, Netbeans est malin et par défaut fait du deploy-on-save. Il faut donc désactiver l’option dans les propriétés du projet pour ne pas interférer avec JRebel.

La configuration du fichier rebel.xml, à placer dans WEB-INF/classes/, va ressembler à ceci :


1
2
3
4
5
6
7
8
9
<?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="D:\NetBeansProjects\WebApplication1\build\web\WEB-INF\classes"/>
    </classpath>
</application>

On demande donc de prendre à chaud tout ce qui va changer dans le répertoire où sont placées les classes compilées.

Dans les logs du démarrage de glassfish, on peut ainsi voir :

1
INFO: JRebel: Directory 'D:\NetBeansProjects\WebApplication1\build\web\WEB-INF\classes' will be monitored for changes.

Procédons à une modification mineure du corps de la méthode processRequest() :


1
2
// On a changé le message affiché
out.println("Congratulations, the result is " + ((result == null) ? "unpredictable" : result));

Après compilation du fichier et rafraîchissement de la page dans le navigateur, on obtient immédiatement le changement :

Modification à chaud du corps d'une méthode

Dans le cas présent, JRebel a fait l’équivalent du hotswapping standard de la JVM, mais sans avoir besoin d’être en mode debug.

Un cran au-dessus : ajout de méthodes / classes

Amusons-nous maintenant dans le domaine de l’inédit. Pour satisfaire notre envie de separation of concerns, on va externaliser le code métier dans une classe différente de la servlet :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Calc {

    public static Integer add(Integer a, Integer b) {
        return a + b;
    }

    public static Integer substract(Integer a, Integer b) {
        return a - b + 2;
    }
}

// et dans la servlet :
if (operation.equals("add")) {
    result = Calc.add(a, b);
} else if (operation.equals("sub")) {
    result = Calc.substract(a, b);
}

On enregistre, on compile et on vérifie : la nouvelle classe (et indirectement de nouvelles méthodes) a été prise en compte automatiquement (pour en être sûr, on peut bidouiller l’addition pour qu’elle calcule a + b + 1 par exemple).

Et les ressources ?

Pour l’instant nous nous sommes contentés de modifier du code Java. Et si on essayait des fichiers properties ? Ca tombe bien, notre client vient justement de nous faire part de son vœu de pouvoir internationaliser le nom des opérations add et sub !

On va donc utiliser un ResourceBundle :


1
2
3
4
5
if (operation.equals(ResourceBundle.getBundle("operations").getString("add"))) {
    result = Calc.add(a, b);
} else if (operation.equals(ResourceBundle.getBundle("operations").getString("sub"))) {
    result = Calc.substract(a, b);
}
1
2
3
# operations.properties
add = add
sub = sub

On compile, on rafraîchit et là… c’est le drame !


1
2
3
4
5
6
7
8
9
10
11
WARNING: StandardWrapperValve[Calculator]: PWC1406: Servlet.service() for servlet Calculator threw exception
java.util.MissingResourceException: Can't find bundle for base name operations, locale en_GB
        at java.util.ResourceBundle.throwMissingResourceException(ResourceBundle.java:1521)
        at java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1260)
        at java.util.ResourceBundle.getBundle(ResourceBundle.java:715)
        at com.excilys.blog.jrebel.servlet.Calculator$$M$6334694c.processRequest(Calculator.java:38)
        at com.excilys.blog.jrebel.servlet.Calculator$$A$6334694c.processRequest()<generated>
        at com.excilys.blog.jrebel.servlet.Calculator.processRequest(Calculator.java:51)
        at com.excilys.blog.jrebel.servlet.Calculator$$M$6334694c.doGet(Calculator.java:65)
        at com.excilys.blog.jrebel.servlet.Calculator$$A$6334694c.doGet()<generated>
        at com.excilys.blog.jrebel.servlet.Calculator.doGet(Calculator.java:65)

Et oui, les ressources sont cherchées dans le répertoire build\web\WEB-INF\classes\operations.properties, hors comme on ne fait pas de build complet le fichier properties qu’on vient de créer ne s’y trouve pas encore :-). Pas de souci, on le copie à la main (ou on fait un build complet, mais c’est plus long), et là ça marche ! Remarquez au passage que les classes mentionnées dans la stacktrace ont des noms un peu bizarres, certaines sont marquées <generated> : cela correspond à ce que j’ai expliqué plus haut au sujet du fonctionnement interne de JRebel.

Zut, le client vient de décider qu’à présent, on aura des noms d’opérations en français. Pas de souci, on a juste à changer notre properties (celui dans le dossier build/, sauf si vous décidez de monitorer également la version dans src/), et les changements sont pris à chaud :

1
2
INFO: JRebel: Reloading class 'com.excilys.blog.jrebel.servlet.Calculator'.
INFO: JRebel: Reloaded bundle file:/D:/NetBeansProjects/WebApplication1/build/web/WEB-INF/classes/operations.properties

Encore une killer-feature : le plugin Spring

Comme évoqué dans les principes de fonctionnement, JRebel supporte entre autres les changements à chaud de configuration du framework Spring grâce à un plugin dédié. Voyons un peu comment ça fonctionne. Après avoir supprimé le static des méthodes de la classe Calc, j’ai décidé d’en faire un bean Spring :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

      xsi:schemaLocation="http://www.springframework.org/schema/beans
                                  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">


    <bean id="calc" class="com.excilys.blog.jrebel.Calc" />

    <bean class="org.springframework.web.context.support.ServletContextAttributeExporter">
        <property name="attributes">
            <map>
                <entry key="calcBean">
                    <ref bean="calc"/>
                </entry>
            </map>
        </property>
    </bean>
</beans>

Ce bean est ensuite récupéré dans la servlet de la manière suivante :

1
2
3
Calc calc = (Calc)getServletContext().getAttribute("calcBean");
//...
result = calc.add(a, b);

Je vous passe la configuration du web.xml, elle est tout à fait standard. Cependant JRebel ne permet pas de prendre à chaud les changements dans web.xml, on va donc être obligés de redéployer notre application (mais on le fera plus après, promis :-D). Redéploiement et rafraîchissement de la page web, ouf, tout fonctionne normalement.

Pour tester le plugin Spring, on va introduire une modification mineure : renommer l’attribut du contexte permettant de récupérer bean.

1
2
3
4
// dans applicationContext.xml, on change la balise <entry> en
//   <entry key="myCalcBean">
// et dans le code Java, on a donc :
Calc calc = (Calc)getServletContext().getAttribute("myCalcBean");

Compilation, rafraîchissement (en ces temps de fortes chaleurs…), applaudissements. On vient de bénéficier d’une modification de la configuration de Spring sans avoir à redéployer !

1
2
3
4
5
INFO: JRebel: Reloading class 'com.excilys.blog.jrebel.servlet.Calculator'.
INFO: JRebel-Spring: Reloading Spring bean definitions in 'file:/D:/NetBeansProjects/WebApplication1/build/web/WEB-INF/classes/applicationContext.xml'.
INFO: Loading XML bean definitions from file [D:\NetBeansProjects\WebApplication1\build\web\WEB-INF\classes\applicationContext.xml]
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@4cea16: defining beans [org.springframework.web.context.support.ServletContextAttributeExporter#0,calc,org.springframework.web.context.support.ServletContextAttributeExporter#1]; root of factory hierarchy
INFO: Exported ServletContext attribute with name 'myCalcBean'

Conclusion

Résumons un peu. Avec un seul déploiement (parce que je m’y suis pris trop tard, avouons-le), on a réussi à prendre en compte immédiatement des modifications de méthodes, des ajouts des classes, des modifications des fichiers properties accédés par un ResourceBundle et surtout des fichiers de configuration de framework (Spring dans cet exemple). Ça en jette, non ? Tout ça pour pas un rond si vos développement durent moins de 30 jours, sinon il faudra débourser une somme relativement modeste (189$ pour une licence annuelle standard).

Le Rebelle aussi utilise JRebel.


Sachez que je n’ai couvert qu’une petite partie de tout ce que JRebel sait faire, histoire de garder un article d’une taille raisonnable. Je vous laisse le soin d’explorer et de tester à fond cette petite merveille pour développeurs !

Petit plus pour les décideurs, à chaque démarrage du serveur, JRebel affiche des estimations sur le précieux temps qu’il vous a fait gagner, de manière à mesurer les gains qu’apportent de tels outils :

1
2
Over the last 3 days JRebel prevented
at least 18 redeploys/restarts saving you about 0.7 hours.

Pour aller plus loin

Share
  1. Bastien HUBERT
    16/07/2010 à 11:19 | #1

    Grand fan de JRebel (je l’ai fait installer partout chez mon client), je complèterai juste en signalant l’existence d’un plugin maven permettant d’autogenerer les rebel.xml.
    Pratique quand vos collègues n’ont pas envie de comprendre comment ca marche, mais en ont quand même marre de redéployer toutes les 30 secondes ;)

    Petit exemple, avec en plus du multi-projet

    org.zeroturnaround
    javarebel-maven-plugin

    generate-rebel-xml
    process-resources

    generate

    ../core/target/classes

    ../editique/target/classes

    En tout cas JRebel procure un véritable gain de temps au quotidien, seul petit regret la non-gestion des fichiers Webflow.
    Officiellement car Webflow est censé le faire tout seul en mode developer, mais d’un avis purement personnel, l’auto redéploiement de webflow n’a vraiment rien à voir avec la qualité de celle de JRebel.

  2. 04/10/2018 à 15:22 | #2

    I am now not positive where you are getting your information, however good topic.

    I must spend a while learning more or understanding more.
    Thank you for fantastic information I used to be on the lookout for
    this information for my mission.

  1. 16/07/2010 à 11:25 | #1