Recharger ses applications avec Spring Loaded

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.

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é.

Utilisation

Les utilisations possibles des agents Java sont :

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 :

Manifest-Version: 1.0
Premain-Class: fr.ippon.agent.LoggingAgent

La classe de l’agent Java :

public class LoggingAgent {

   public static void premain(String agentArgs, Instrumentation instrumentation) {
       System.out.println("Entering premain method");
       instrumentation.addTransformer(new LoggingClassFileTransformer());
   }
}

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” :

public class LoggingClassFileTransformer implements ClassFileTransformer {

   public byte[] transform(ClassLoader loader, String className, Class< ?> classBeingRedefined,
                           ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
       System.out.println("Transforming class " + className);
       return classfileBuffer;
   }
}

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

Chargement d’un agent

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

–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.

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

Spring Tool Suite

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

Spring Tool Suite - Configuration

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.

IntelliJ Idea

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

Configuration - Intellij IDEA

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.

Maven

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

<dependency>
<groupid>org.springframework</groupid>
<artifactid>springloaded</artifactid>
<version>1.2.3.RELEASE</version>
</dependency>

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

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 :

@RestController
public class ReloadableController {

   @RequestMapping(value = "/home", method = RequestMethod.GET)
   public String home() {
       return "home";
   }

}

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

Spring Loaded - 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” :

@RestController
public class ReloadableController {

    @RequestMapping(value = "/home", method = RequestMethod.GET)
    public String home() {
        return "reloaded-home";
    }

}

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 :

Spring Reloaded - Rechargement du mapping de Spring MVC

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

Spring Loaded - contrôleur "rechargé"

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 effectuer différentes actions lors de celui-ci. Il suffit pour cela de créer une classe implémentant l’interface ReloadEventProcessorPlugin 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.

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%3Aissue+is%3Aopen

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 permettre 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 :