Bascule sur Spring Boot d’une application Java legacy

Spring Boot est un excellent accélérateur et cadre pour les applications spécifiques Java. J’ai eu l’occasion d’y faire passer une application dite « legacy » : c’est plus compliqué que de partir de zéro, mais ça vaut le coup.

Quelques avantages de Spring Boot

  • Proposer des librairies/versions cohérentes et testées
  • Proposer des choix de librairies et de paramétrage pour un grand nombre de besoins. C’est un framework « opinioné », mais qui permet toujours de faire d’autres choix
  • Aider à la gestion des dépendances Maven
  • Normer les projets
  • Embarquer le serveur d’application (tomcat ou autre) : lancement via un main Java, qui embarque tomcat, plutôt que de déployer dans un tomcat installé au préalable
  • Faire le maximum de configuration automatiquement … tout en laissant la main si besoin
  • Proposer des métriques, « health check », et une manière d’externaliser la configuration
  • Faciliter le « on-boarding » des nouveaux développeurs, car ils retrouvent un cadre standard plutôt que de devoir comprendre comment l’application est construite
  • Faciliter la réutilisation du contexte Spring du run dans les tests unitaires
  • etc.

Bascule en deux temps

Nous avons fait le choix de faire la migration en deux temps : basculer d’abord sur une version de Spring Boot qui a des librairies dans des versions proches de celles utilisées par l’application actuelle (Spring Boot 1.3.8 en l’occurrence), puis faire l’upgrade en Spring Boot 2 (plus tard).

C’est un choix qui permet de limiter les risques liés aux mises à jour des librairies concernées (migration Hibernate 4 vers 5 et passage en JPA, notamment). L’application ayant un très gros historique, on ne pouvait pas tout faire en même temps.

Principales étapes

Créer un squelette via https://start.spring.io/, puis y mettre le code existant. Ca a été aussi l’occasion d’un grand ménage de printemps dans les dépendances Maven et leurs versions : supprimer les dépendances inutiles, et s’appuyer sur les versions proposées par Spring Boot (si elles ne sont pas trop éloignées de l’existant).

Il faut enlever la déclaration de la datasource (pour s’appuyer sur celle créée par Spring Boot en autoconfiguration), enlever aussi l’éventuelle exposition JMX (elle aussi faite par Spring Boot), tout en gardant les déclaration de MBeans.

On a gardé dans un premier temps la possibilité de déployer sous Tomcat, en introduisant un profil « tomcat », avec, dans le fichier de paramétrage correspondant, la définition de la datasource via le JNDI : spring.datasource.jndi-name=java:comp/env/jdbc/dataSource

Astuce : le fichier Application.java (du squelette Spring Boot) peut initialiser Spring Boot à la fois quand il est lancé en tant que main et quand il est lancé via Tomcat. Il suffit qu’il implémente SpringBootServletInitializer. Exemple :

@SpringBootApplication(exclude = HibernateJpaAutoConfiguration.class)
public class Application extends SpringBootServletInitializer {
    
    /**
     * Code utilisé lorsque l'application est lancée via Tomcat
     */
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class) ;
    }

    /**
     * Code utilisé lorsque l'application est lancée en main Java
     */
    public static void main(String[] args) {
            SpringApplication.run(new Class[] { Application.class}, args) ;
    }

}

Il ne faut plus créer d’ApplicationContext à la main, mais récupérer celui déjà créé par Spring Boot : WebApplicationContextUtils.findWebApplicationContext(servletContext)

Il faut supprimer les éventuels @EnableWebMvc (qui empêchent la WebMvcAutoConfiguration de Spring Boot)

Les TUs doivent être annotés pour utiliser le contexte Spring global (au lieu de l’importer manuellement) :

@SpringApplicationConfiguration(Application.class)
@WebAppConfiguration

Difficultés rencontrées

On a eu des problèmes sur des fichiers que l’application allait lire sur le filesystem. En passant sur Spring Boot, c’est dans le classpath qu’il faut que l’application le trouve (quand on lance en main Java).

La solution a été de modifier un peu le code qui lit ces fichiers, pour utiliser la classe org.springframework.core.io.Resource au lieu de java.io.File ou java.net.URL.

Petit piège sur la mise en cache du contexte Spring par Spring Boot pour les TUs : il ne fonctionne pas si on a configuré le maven-surefire-plugin pour qu’il « forke ». Il a donc fallu modifier un peu la conf Maven pour enlever les attributs forkCount.

Attention au fait que le token de filtering Maven est surchargé par Spring Boot, qui utilise @ au lieu de ${…}.

Attention aussi au fait que Spring Boot s’appuie sur logback pour gérer les logs. Il nous est arrivé que ça fasse un joli micmac avec les librairies de logging déjà utilisées par l’application.

Certains tests unitaires, basés sur des Mocks, ne pouvaient pas facilement être exécutés en utilisant le contexte Spring Boot de run. Ca n’est pas forcément un souci : ils peuvent continuer à tourner avec leur contexte Spring custom (même si on ne bénéficie pas de la mise en cache du contexte pour ceux-là)

Résultat

Division par 2 du temps de build+TU (grâce à la mise en cache du contexte Spring)

Aucun impact fonctionnel, les utilisateurs n’ont pas vu la différence (c’était le but)

… Et tous les avantages cités au début de cet article

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *