Tutoriel pour apprendre à recharger ses applications avec Spring Loaded

| Florian Lopes | Ippon Technologies |

Spring Loaded est un agent Java permettant de recharger les classes « à chaud », c'est-à-dire sans avoir à redémarrer la JVM. Cet agent modifie le bytecode des classes lors de leur chargement afin de les rendre rechargeables. Spring Loaded est ainsi capable de répercuter des changements sur des méthodes, des champs ou encore des constructeurs, lorsqu'il détecte un changement des fichiers « .class ». Mieux encore, il permet de recharger un contexte Spring MVC, ce qui permet par exemple de modifier le mapping des contrôleurs (l'adresse à laquelle un contrôleur est accessible), sans avoir à redémarrer l'application pour que les changements soient pris en compte.

Pour réagir à ce tutoriel, un espace de dialogue vous est proposé sur le forum : Commentez

- Titre : Tutoriel pour apprendre à recharger ses applications avec Spring Loaded

- Auteurs : Florian Lopes, Ippon Technologies

- Parution : 18 mars 2016

- Licence : Copyright ® 2016 Florian LOPES. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Les agents Java

- I-A. Utilisation

- I-B. Développer un agent

- I-C. Chargement d'un agent

- II. Utilisation de Spring Loaded

- II-A. Spring Tool Suite

- II-B. IntelliJ Idea

- II-C. Maven

- II-D. Spring Loaded in action

- II-E. Limitations

- III. En résumé

- IV. Conclusion et remerciements

I - Les agents Java

Les agents Java permettent d'instrumenter des programmes qui sont lancés sur la JVM. Au démarrage de celle-ci, ils vont pouvoir intercepter le chargement des classes et ainsi en modifier le bytecode associé.

I-A - Utilisation

Les utilisations possibles des agents Java sont :

- le rechargement des classes au runtime (exemple : avec Spring Loaded) ;

- visualisation de l'utilisation de la mémoire : https://devcenter.heroku.com/articles/java-memory-issues#memory-logging-agent ;

- exemple sur GitHub : https://github.com/heroku/heroku-javaagent ;

- surveiller les performances d'une application Java dans Heroku (exemple : avec l'add-on NewRelic).

I-B - Développer un agent

Trois choses sont nécessaires à la conception d'un agent Java :

- un fichier manifest, qui doit préciser la classe à charger pour lancer l'agent ;

- une classe qui charge l'agent, avec une méthode « premain(String args, Instrumentation inst) » ;

- l'agent doit être déployé sous forme de jar.

Ci-dessous, un exemple d'agent Java :

Le fichier MANIFEST.MF :

1. Manifest-Version: 1.0

2. Premain-Class: fr.ippon.agent.LoggingAgent

La classe de l'agent Java :

1. public class LoggingAgent {

3. public static void premain(String agentArgs, Instrumentation instrumentation) {

4. System.out.println("Entering premain method");

5. instrumentation.addTransformer(new LoggingClassFileTransformer());

6. }

7. }

La méthode premain() est quelque peu similaire à la méthode main().

Le premier paramètre permet de récupérer les arguments passés en ligne de commande. Le second, quant à lui, contient plusieurs méthodes permettant de modifier les classes chargées.

Voici un exemple basique de “ClassFileTransformer” :

1. public class LoggingClassFileTransformer implements ClassFileTransformer {

3. public byte[] transform(ClassLoader loader, String className, Class< ?> classBeingRedefined,

4. ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

5. System.out.println("Transforming class " + className);

6. return classfileBuffer;

7. }

8. }

Pour aller plus loin dans la manipulation de bytecode, il est possible d'utiliser Javassist.

I-C - Chargement d'un agent

Pour charger un agent, il suffit de rajouter l'option “-javaagent” à la commande Java :

1. -javaagent:{javaagent}.jar ...

Pour en charger plusieurs, il suffit de rajouter les mêmes paramètres, ils seront lancés dans l'ordre dans lequel ils ont été déclarés.

II - Utilisation de Spring Loaded

Pour utiliser Spring Loaded, il faut d'abord télécharger l'agent Java à cette adresse : https://github.com/spring-projects/spring-loaded/releases.

II-A - Spring Tool Suite

Le rechargement d'une application Spring Boot avec Spring Tool Suite est aisé, il suffit de spécifier les arguments de la VM suivants :

L'option “-noverify” permet de désactiver la vérification du bytecode généré, et ainsi d'éviter des exceptions liées à cette vérification.

II-B - IntelliJ Idea

La configuration avec IntelliJ Idea est tout aussi simple, là encore, il faut ajouter les arguments de la VM suivants :

La prise en compte des modifications est quelque peu différente de celle avec Spring Tool Suite : IntelliJ Idea ne compilant pas les classes automatiquement, il est nécessaire de recompiler manuellement les classes modifiées (Build -> Compile `*.java' ou CTRL + F9), afin de bénéficier du rechargement.

II-C - Maven

Pour recharger une application Spring Boot avec Maven, il suffit d'ajouter la dépendance suivante au plugin spring-boot-maven-plugin :

1. <dependency>

2. <groupid>org.springframework</groupid>

3. <artifactid>springloaded</artifactid>

4. <version>1.2.3.RELEASE</version>

5. </dependency>

Le lancement de l'application Spring Boot avec Maven est fait grâce à la commande suivante : mvn spring-boot:run.

II-D - Spring Loaded in action

Pour démontrer la prise en compte des changements apportés dans une application, testons Spring Loaded à l'aide d'une simple application Spring Boot qui comporte un contrôleur REST Spring MVC. Celui-ci renvoie une simple chaîne de caractères :

1. @RestController

2. public class ReloadableController {

3. @RequestMapping(value = "/home", method = RequestMethod.GET)

4. public String home() {

5. return "home";

6. }

7. }

Après avoir lancé l'application, la page à l'adresse « localhost:8080/home » affiche la chaîne de caractères « home ».

Pour vérifier la prise en compte des changements dans un contrôleur de l'application, modifions la chaîne de caractères retournée par celui-ci, pour la remplacer par “reloaded-home” :

1. @RestController

2. public class ReloadableController {

3. @RequestMapping(value = "/home", method = RequestMethod.GET)

4. public String home() {

5. return "reloaded-home";

6. }

7. }

Après modification de la classe “ReloadableController” (et compilation manuelle avec IntelliJ Idea), nous pouvons voir dans les logs que Spring Loaded a détecté la classe modifiée (ici notre contrôleur accessible à l'adresse “/home”) et l'a bien rechargée :

En rafraîchissant la page “/home”, le contrôleur retourne bien la nouvelle valeur, sans avoir redémarré l'application :

Spring Loaded prend aussi en compte la modification des adresses des contrôleurs ou encore l'ajout de paramètres dans leurs méthodes.

TIP : il est possible de développer un plugin pour Spring Loaded qui sera notifié de chaque rechargement, et ainsi pourra effectuer différentes actions lors de celui-ci. Il suffit pour cela de créer une classe implémentant l'interface ReloadEventProcessorPluginet et de l'activer grâce à la méthode registerGlobalPlugin de la classe SpringLoadedPreProcessor. Cette classe sera alors appelée lorsque Spring Loaded effectuera un rechargement.

II-E - Limitations

Spring Loaded comporte les limitations suivantes :

- il n'est pas possible de modifier une hiérarchie de classes ;

- il ne recharge pas la configuration de Spring Security ;

- il ne supporte pas l'ajout d'un nouveau contrôleur ;

- on ne peut pas changer le niveau de log ;

- le rechargement des classes n'est pas disponible pour les applications utilisant les préfixes de package suivants :

- org/springframework/,

- org/springsource/loaded/,

- et bien d'autres :https://github.com/spring-projects/spring-loaded/wiki/Basic-usage-information.

Pour des problématiques d'utilisations particulières, n'hésitez pas à consulter les « issues » sur GitHub : https://github.com/spring-projects/spring-loaded/issues?page=2&q=is:issue+is:open.

III - En résumé

Spring Loaded, aussi utilisé par Grails2, permet de gagner un temps de développement précieux pour des applications simples, notamment grâce à la prise en compte de changements dans les contrôleurs Spring MVC.

Spring Loaded constitue une alternative gratuite à JRebel, dans une certaine limite. Pour recharger des applications plus complexes (notamment avec l'utilisation de Spring Security), il est nécessaire de se tourner vers la solution JRebel qui supporte les serveurs d'applications ainsi que de nombreux frameworks : http://manuals.zeroturnaround.com/jrebel/misc/integrations.html. Pour permettre toutes ces fonctionnalités, JRebel se base sur les classloaders pour accepter le rechargement des classes/frameworks.

Une autre alternative intéressante à étudier est la solution de JHipster. Basée sur Spring Loaded, jhipster-loaded va beaucoup plus loin dans le rechargement des classes et est capable de prendre en compte des changements apportés à une configuration Liquibase, Jackson, JPA ou encore Mongo. Le projet est disponible ici : https://github.com/jhipster/jhipster-loaded.

Le projet d'exemple sur les agents Java ainsi que l'application Spring Boot sont disponibles à ces adresses :

- https://github.com/ippontech/java-agent-example ;

- https://github.com/ippontech/spring-loaded-example.

IV - Conclusion et remerciements

Cet article a été publié avec l'aimable autorisation de Florian Lopes. L'article original peut être vu sur le blog/site de Ippon.

Nous tenons à remercier Jacques Jean pour sa relecture orthographique et Malick SECK pour la mise au gabarit.