Pages

mercredi 12 novembre 2014

Spring-Loaded comment gagner en productivité quand on développe en Java

Etes vous développeur Java ?
Si oui n'avez vous jamais pester contre votre application qui était trop longue à charger ?
Moi oui et j'ai trouvé une solution simple le rechargement à la volée. 

Si votre DSI est ouverte pour allouer des budgets aux développeurs, je vous conseillerai JRebel de la société ZeroTurnaroud qui est certainement l'outil le plus au point. Si vous êtes dans mon cas où il est dur de faire comprendre à vos décideurs qu'il est important d'avoir des environnements de travail performants, vous pouvez vous pencher sur Spring Loaded, outil Open Source développé par les équipes Spring. 



Spring-Loaded est un agent Java permettant à la JVM de recharger les fichiers class modifiés. Le remplacement à chaud du code dans une JVM  est une opération risquée et donc très limitée. Spring-Loaded modifie légèrement les fichiers class avant leur chargement pour qu'ils puissent être rechargés plus tard. Ainsi vous pouvez modifier les différents objets de votre application sans avoir à relancer votre application à chaque modification. 

Si vous voulez utiliser Spring-Loaded sur votre projet, vous pouvez le récupérer dans le repo central Maven. Il est important d'utiliser la même version de JVM dans votre processus de build et dans votre IDE.

Je vais montrer plusieurs exemples qui sont également à disposition sous Github. L'application est une petite webapp Spring-Boot exposant un controller REST

@RestController
public class MyController {

    @RequestMapping("/hello/{name}")
    public String listEnv(@PathVariable String name) {

        return "Hello " + name;
    }
}

Le but est par exemple de changer le retour de ce service REST et de constater que les changements sont pris en compte sans avoir à redémarrer l'application.

Spring-Loaded dans un projet IntelliJ utilisant Spring-Boot géré par Gradle
Si le cycle de vie de votre application Spring Boot est géré avec Gradle et si vous utilisez IntelliJ comme IDE, la configuration sera vraiment simple. Modifier votre fichier build.gradle en ajoutant les informations ci dessous

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:1.1.8.RELEASE"
        classpath 'org.springframework:springloaded:1.2.0.RELEASE'
    }
}

apply plugin: 'idea'
apply plugin: 'java'
apply plugin: 'spring-boot'

idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

Ces quelques lignes suffisent à activer l'agent Java lors du démarrage de l'application. Modifiez par exemple le contrôleur REST, compilez vos modifications (Ctrl+Maj+F9) et vérifiez le résultat de votre service REST.

Spring-Loaded en utilisant Maven
Tout le monde n'utilise pas Gradle ni IntelliJ. Si votre projet est géré via Maven vous pouvez utiliser le plugin spring-boot-maven-plugin. Le pom complet est disponible ici. En voici un aperçu

<profiles>
    <profile>
        <id>springloaded</id>
        <activation>
            <property>
                <name>springloaded</name>
            </property>
        </activation>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>springloaded</artifactId>
                <version>${spring.loaded.version}</version>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <agent>${settings.localRepository}/org/springframework/springloaded/${spring.loaded.version}/springloaded-${spring.loaded.version}.jar</agent>
                        <jvmArguments>-noverify</jvmArguments>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

Vous pouvez lancer la commande suivante pour initier votre application
mvn spring-boot:run -Pspringloaded

Pour lancer l'agent nous devons faire référence au jar spring-loaded. C'est pourquoi j'ai déclaré une dépendance Maven et fait référence au jar chargé dans le repository local

Spring-Loaded sans rien...
Et si vous souhaitez aller au plus simple vous pouvez simplement télécharger spring-loaded en local sur votre poste (par exemple dans /media/mydisk/src/javamind-spring-loaded/lib). Au moment de lancer votre application vous pouvez préciser à la JVM l'agent Java à utiliser.

Par exemple



Erreur courante
Vous pouvez avoir ce type de stack trace après une compilation. Comme l'agent modifie le bytecode il est conseillé d'utiliser l'option -noverify lors du lancement de l'agent

java.lang.VerifyError: Expecting a stackmap frame at branch target 24
Exception Details:
Location:
com/javamind/example/MyController$DOvLhDdA.__execute([Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object; @6: ifeq

Reason:
Expected stackmap frame at this location.
  Bytecode:
  0000000: 2d12 1ab6 0020 9900 122c c000 222b 1223
  0000010: 32c0 001c b800 16b0 2d12 25b6 0020 990
  0000020: 0c2c c000 22b8 0012 01b0 b200 29b6 002f
  0000030: 2b2c 2db9 0031 0400 b0                 
 at java.lang.Class.getDeclaredConstructors0(Native Method)
 at java.lang.Class.privateGetDeclaredConstructors(Class.java:2493)
 at java.lang.Class.getConstructor0(Class.java:2803)
 at java.lang.Class.newInstance(Class.java:345)
 at org.springsource.loaded.CurrentLiveVersion.define(CurrentLiveVersion.java:133)

Conclusion
Tous les changements ne sont pas pris en compte mais vous pouvez faire beaucoup de choses dans votre projet sans avoir à relancer votre application. Je ne l'ai pas fait mais il est possible décrire ses propre plugins pour adapter le comportement de Spring-Loaded à votre contexte.

Pour plus d'info suivez le projet sous Github. La documentation Spring est encore assez pauvre sur le sujet mais vu l'intérêt d'un tel outil pour les développeurs, j'espère que les choses changeront rapidement.


1 commentaire:

  1. Petit retour dessus, j'ai remarqué des erreurs notamment dans le support des lambda expressions du type : MyObject::MyMethod
    J'ai de belles stacktrace m'indiquant que la méthode MyMethod n'est pas une méthode statique de l'objet dans lequel j'utilise cette notation (et qui n'est pas MyObject)

    (j'ai pas forcément été clair ^^)

    Bon sinon c'est pratique

    RépondreSupprimer

Remarque : Seul un membre de ce blog est autorisé à enregistrer un commentaire.