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 :
- 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
- Monitorer les performances d’une application Java dans Heroku (exemple avec l’add-on NewRelic)
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 :
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 :
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 ».
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 :
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 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 :