JavaEE – Java & Moi https://javaetmoi.com Développeur Java, Spring & co, et fier de l'être Sun, 25 Aug 2024 15:59:46 +0000 fr-FR hourly 1 https://wordpress.org/?v=6.9.4 https://javaetmoi.com/wp-content/uploads/2022/05/cropped-java-icon-32x32.png JavaEE – Java & Moi https://javaetmoi.com 32 32 Compatibilité Jakarta EE 9 de vieux frameworks https://javaetmoi.com/2024/08/compatibilite-jakarta-ee-9-de-vieux-frameworks/ https://javaetmoi.com/2024/08/compatibilite-jakarta-ee-9-de-vieux-frameworks/#respond Sun, 25 Aug 2024 15:54:14 +0000 https://javaetmoi.com/?p=2374 Continuer la lecture de Compatibilité Jakarta EE 9 de vieux frameworks ]]> De Java EE à Jakarta EE

En 2017, Oracle a fait don de la spécification Java EE (précédemment connu sous le nom de J2EE) à la fondation Eclipse. Java EE regroupe différentes API utilisées aussi bien par des serveurs d’applications, des containers de servlets et des frameworks comme Quarkus ou Spring : Servlet, JSP, JSF, JPA, JTA, JAX-WS, JAX-RS, JAXB, WebSocket, Bean Validation, CDI, EL …

Sous l’égide d’Eclipse, Java EE a été rebaptisé Jakarta EE. La fondation a récupéré la base de code Java et les TCK. En 2019 est sortie une version Jakarta EE 8 pleinement compatible avec Java EE 8. Comme seul changement notable pour les dév, le groupId des artefacts Maven a été renommé de javax à jakarta. Le patch du numéro de version a été incrémenté. A titre d’exemple, l’artefact jakarta.faces:jakarta.faces-api:2.3.1 est identique à javax.faces:javax.faces-api:2.3. Pas si anodin, ce changement de GAV Maven fait que notre outil de build peut être amené, via le mécanisme de dépendances transitives, à placer dans le classpath deux mêmes artefacts ayant des groupId différents. Les exclusions maven permettent de corriger le tir.

En décembre 2020, la communauté Java est secouée par la sortie de Java EE 9. 20 ans de rétrocompatibilité s’écroulent. Oracle a souhaité conserver la marque Java. Les packages javax.* de la spécification Java EE ont été renommés en jakarta.*. Certains sous-packages ont également été renommés. 
Pour exemple, la classe Marshaller de l’API JAXB change de package : de javax.xml.bind.Marshaller vers jakarta.xml.bind.Marshaller

A cette occasion, le numéro de version majeur a été incrémenté.
Les coordonnées Maven Jakarta EE 8 de l’API JSF jakarta.faces:jakarta.faces-api:2.3.1 changent en jakarta.faces:jakarta.faces-api:3.0.0 sous Jakarta EE 9.

A noter que les packages javax du JDK et qui n’appartiennent donc pas à Java EE ne sont pas renommés. On peut citer : javax.sql, javax.swing, javax.naming, javax.transaction.xa et javax.naming.

Ce changement de package Java est on ne peut plus impactant :

  1. Le code Java non migré ne fonctionne pas avec un container/runtime plus récent
  2. Un ancien container/runtime ne fonctionne pas avec du code Java récent migré

Ce changement a impacté tout l’écosystème Java : les projets Open Source, le code propriétaire / métier, les IDE, les outils de build …

Quatre ans plus tard, la grande majorité des projets Open Source actifs proposent une version de leurs artefacts compatibles jakarta. Les frameworks les plus utilisés comme Quarkus ou Spring étaient attendus par leur communauté et l’ont fait relativement rapidement. Par exemple, Spring Framework 6.0 et Spring Boot 3.0 sont tous les deux sortis en novembre 2022.
Pour migrer vers Jakarta EE 9 et le package jakarta, un projet reposant lui-même sur d’autres librairies tierces doit attendre que ses dépendances soient migrées. Cela a créé une certaine inertie dans l’écosystème Java. Par exemple, le framework Apache CXF, qui offre un support pour Spring, a dû attendre la sortie de Spring Framework 6 pour sortir à son tour en décembre 2022 la version CXF 4.

Migrer des applications legacy

Prenons l’exemple d’un SI composé de dizaines d’applications Java qui, pour des questions de sécurité et d’obsolescence, doivent migrer sur un Tomcat 10.

Les applications les plus modernes, basées sur Spring Boot, Quarkus ou Micronaut, s’appuient en général sur des stacks techniques récentes et actives. Migrer de Spring Boot 2.7 à Spring Boot 3 ne pose pas de difficultés majeures. Armé du guide de migration Spring Boot 3 et d’outils comme la recette OpenRewrite Migrate to Spring Boot 3.0, le projet Spring Boot Migrator (SBM) ou bien encore l’IntelliJ IDEA’s migration tool, les développeurs sont assistés dans leur travail et trouvent les ressources nécessaires sur le Net.

A contrario, les applications Java les plus anciennes du SI, pouvant avoir jusqu’à 25 ans, peuvent continuer pour certaines à s’appuyer sur des frameworks et des librairies non maintenus, abandonnés depuis des années par leurs créateurs. Lorsque cela est possible, identifier puis migrer vers une alternative est recommandé. Par exemple, l’équipe projet Dozer invite à migrer vers MapStruct ou ModelMapper et propose même un plugin IntelliJ pour faciliter la tâche.
Qu’en est-il de frameworks plus structurants ? Je pense notamment à de vieux frameworks frontends sur lesquels sont conçus des centaines d’écrans d’applications de gestion.

Par exemple, Struts 1 n’est pas compatible Jakarta EE 9 et les nouveaux packages en jakarta.* Il s’appuie sur l’API javax.servlet.http.HttpServlet du package javax.servlet. Le conteneur web Tomcat 10 manipule quant à lui la classe jakarta.servlet.http.HttpServlet. Même chose pour Richfaces abandonné par JBoss depuis 2016.

Migrer les écrans d’une application de Struts 1 vers Struts 6, React ou Angular est envisageable. Le cout en sera nettement plus élevé. Les délais aussi. L’automatisation aura ses limites. Autre solution : utiliser Struts 1 Reloaded dont la version 1.5.0 est compatible Jakarta EE 9. Maintenu par un seul et unique développeur, la base de code a divergé de l’original. Il pourrait y avoir des régressions.

Faute de budget conséquent, ces applications seraient-elles vouées à rester ad vitam æternam sur du Spring Boot 2 ? Non, la suite de cet article explique comment automatiser la compatibilité jakarta de vieux frameworks et de vielles librairies.

Solution technique

Les développeurs du conteneur Tomcat ont adressé cette problématique lors de la sortie de Tomcat 10. En effet, Tomcat 10 sait convertir une application web existante de Java EE 8 à Jakarta EE 9 au moment du déploiement en utilisant l’outil de migration Apache Tomcat pour Jakarta EE. Pratique, cet outil peut être utilisé en dehors de Tomcat, sous forme d’un jar auto-exécutable ou d’une tâche Ant. Contrairement à ce que son nom pourrait laisser penser, il n’est pas lié au conteneur Tomcat et pourrait être utilisé pour cibler des versions récentes de Jetty et de Wildfly.

Le projet tomcat-jakartaee-migration effectue tous les changements nécessaires pour migrer une application de Java EE 8 vers Jakarta EE 9 en renommant chaque package Java EE 8 vers son remplaçant Jakarta EE 9. Cela inclut les références aux package dans les classes, les constantes de type String, les fichiers de configuration, les JSP, les TLD …
Tous les packages javax.* ne font pas partie de Java EE. Seuls ceux définis par Java EE sont déplacés vers l’espace de noms jakarta.*.
Il n’est pas nécessaire de migrer les références aux schémas XML. Les schémas ne font pas directement référence aux packages javax et Jakarta EE 9 continuera à supporter l’utilisation des schémas de Java EE 8 et antérieurs.

Cet outil propose 2 profils : le profil partiel TOMCAT ciblant les conteneurs web comme Tomcat et Jetty et le profil EE ciblant toutes les dépendances Java EE 8.
L’outil sait parcourir différents types d’archives : jar, zip, war … Via ses converters (TextConverter, ClassConverter, ManifestConvert), il sait également manipuler plusieurs formats de fichiers : les classes compilées contenues dans les JAR comme le code source Java, les fichiers XML, JSON et properties, les pages JSP (jsp, jspxf, jspx), les tags JSP (tag, tld, tagx) …

L’outil tomcat-jakartaee-migration peut donc aussi bien travailler sur des JAR de librairies tierces que sur du code source métier qu’on souhaite migrer vers Jakarta EE 9 et même Jakarta EE 10.

Guide d’utilisation

Rendre compatible Jakarta EE 9 des librairies tierces puis les utiliser dans le code métier se fait en 2 étapes :

Etape 1 : migrer les librairies tierces

1. Récupérer le binaire depuis la page https://tomcat.apache.org/download-migration.cgi

2. Executer la ligne de commande suivante (exemple avec jsf-api-1.2_14.jar) :

set MIGRATION_TOOL=C:\dev\jakartaee-migration\lib\jakartaee-migration-1.0.8.jar
set M2_REPO=C:\dev\maven\repository
java -jar %MIGRATION_TOOL% -profile=EE %M2_REPO%\javax\faces\jsf-api\1.2_14\jsf-api-1.2_14.jar %M2_REPO%\javax\faces\jsf-api\1.2_14-jakarta\jsf-api-1.2_14-jakarta.jar

Le fichier JAR jsf-api-1.2_14-jakarta.jar généré est désormais compatible Jakarta EE 9.
Extrait de la classe FacesServlet :

jsf-api-1.2_14.jar compatible Java EE 8

jsf-api-1.2_14-jakarta.jar migré à Jakarta EE 9

package javax.faces.webapp;
 
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import java.io.IOException;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
 
public final class FacesServlet implements Servlet {

package jakarta.faces.webapp;
 
import jakarta.faces.FacesException;
import jakarta.faces.FactoryFinder;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.FacesContextFactory;
import jakarta.faces.lifecycle.Lifecycle;
import jakarta.faces.lifecycle.LifecycleFactory;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.UnavailableException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
 
import java.io.IOException;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
 
public final class FacesServlet implements Servlet {

3. Renouveler l’opération pour le JAR du code source.
Exemple sur jsf-api-1.2_14-sources.jar :

java -jar $MIGRATION_TOOL -profile=EE $M2_REPO/javax/faces/jsf-api/1.2_14/jsf-api-1.2_14-sources.jar $M2_REPO/javax/faces/jsf-api/1.2_14-jakarta/jsf-api-1.2_14-jakarta-sources.jar 

4. Uploader le JAR et ses sources dans le repository binaire d’entreprise (ex : Artifactory ou Nexus Sonatype). Privilégiez l’ajout du suffixe -jakarta au numéro de version Maven à l’utilisation d’un classifier Maven.

Cette étape de migration peut être complètement automatisée par un pipeline CI Jenkins ou GitLab.

Etape 2 : utiliser les librairies tierces migrées

1. Comme pré-requis, le code source de l’application doit avoir commencé sa migration à Jakarta EE 9 (ou supérieur).

2. Une fois les différentes librairies et frameworks migrés et uploadés dans le repository d’entreprise, il est possible de les référencer dans les pom.xml de l’application

3. Il est ensuite nécessaire d’adapter le code métier utilisant les classes de ces librairies qui ont changé de package, au niveau des imports du code source java, mais également dans le fichiers XML. 
Exemple du web.xml référençant jakarta.faces.webapp.FacesServlet :

<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>jakarta.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

Pour y arriver, 4 possibilités s’offrent à nous :

java -jar %MIGRATION_TOOL% -logLevel=FINEST -profile=EE C:\dev\project\my-webapp C:\dev\project\my-webapp-jakarta

Cette dernière option est à privilégier. En attente de prise en compte de la PR #60 de mon collègue Marco pour exclure le sous-répertoire .git et utiliser le répertoire source comme cible.

5. Vérifier que tout compile

mvn clean install

Conclusion

Cette solution présente plusieurs avantages :

  • Simplicité 
  • Cout défiant toute concurrence
  • Réutilisation d’un outil Open Source maintenu par l’équipe Tomcat et massivement éprouvé
  • Automatisation possible

Son principal inconvénient réside dans le fait que l’application continue à utiliser une librairie non maintenue. A moyen termes, trouver un financement pour refondre ou migrer l’application vers une technologie cible reste donc préconisé.

Enfin, d’autres outils que celui d’Apache existe, par exemple Eclipse Transformer. Avant de vous lancer, comparez-les.

Ressources :

]]>
https://javaetmoi.com/2024/08/compatibilite-jakarta-ee-9-de-vieux-frameworks/feed/ 0
Isoler le classloader de son EAR sous JBoss https://javaetmoi.com/2013/01/isoler-classloader-ear-jboss/ https://javaetmoi.com/2013/01/isoler-classloader-ear-jboss/#respond Sat, 05 Jan 2013 09:41:40 +0000 http://javaetmoi.com/?p=520 Continuer la lecture de Isoler le classloader de son EAR sous JBoss ]]> Lors de la migration d’une application d’un serveur d’application vers un autre, il est fréquent d’être confronté à des problématiques de conflits de librairies. C’est par exemple le cas lorsqu’une application initialement déployée sur un Websphere Application Server 6.1  doit migrer sur JBoss 5.1 EAP (version commerciale de JBoss AS).
Pour rappel, WAS 6.1 implémente J2EE 1.4 et s’exécute donc sur Java 5. Quant à JBoss 5.1 EAP, il implémente la norme Java EE 5, embarque donc de nombreuses implémentations des standards tels que JPA 1, JSF 1.2 et JAX-WS 1, et tourne sur Java 6.

Pour illustration, prenons une application s’appuyant sur Hibernate 3.6 pour sa couche de persistance et JAXB 2.2 pour le marshalling lors d’appel de web services.  Ces 2 librairies sont embarquées dans le répertoire lib de son EAR et ne posent pas de problèmes particuliers à WAS 6.1.
Par contre, sur JBoss 5.1 EAP, c’est un tout autre problème. En effet,  son  implémentation JPA repose sur la version 3.3 d’Hibernate. Qui plus est, JAXB 2.1 a été intégrée dans Java 6.
Si vous tentez de déployer une telle application sur un JBoss installé avec la configuration par défaut, il y’a de fortes chances que vous tombiez sur l’une ou l’autre des exceptions suivantes : ClassCastException, NoSuchMethodException, IllegalAccessErrors, VerifyError.
A ce que j’ai compris en parcourant la documentation mais également déduis de mes tests, différents mécanismes permettent d’expliquer ces comportements :

  1. Par défaut, lors du chargement d’une classe, le classloader de l’EAR va commencer par demander à son classloader parent (en l’occurrence celui de JBoss) de trouver la classe. Ainsi, c’est par exemple la classe Session d’Hibernate 3.3 qui sera chargée et non celle de la version 3.6 comme attendu. Il s’agit du comportement standard d’un classloader. Et c’est ce qu’on appelle communément le « j2se classloading compliance ». Sous WAS, cette stratégie de chargement peut être changée en paramétrant le classloader en PARENT_LAST.
  2. Les classes chargées par d’autres applications déployées sur la même instance de JBoss peuvent être partagées par votre application. Par exemple, la console d’admin JBoss admin-console.war embarque sa propre version de Richfaces et de Seam et peut, malgré elle, vous en faire bénéficier.

Solutions étudiées

Pour mener à bien la migration, plusieurs pistes ont été étudiées :

Solutions Inconvénients Avantages
1 Downgrader les versions des frameworks pour utiliser celles embarquées dans JBoss 5.1 Important travail de refactoring pour combler les fonctionnalités manquantes.
Bugs existants récupérés.
Respect de la norme Java EE 5.
Support éditeur maximal.
2 Configurer sur mesure le répertoire d’installation de JBoss (ex : supprimer le support des EJB 3 et de JPA) Mutualisation du répertoire d’installation rendue caduque.
Main sur la production.
Un JBoss qui démarre plus vite.
Pas d’impact sur le code.
3 Isoler le déploiement de l’application Lire la documentation JBoss.
Augmentation possible de la PermGen.
Risque nul.
Simple configuration.
Configuration embarquée dans l’EAR.

Configurer le classloader de l’application

@Copyright JBoss
Classe chargée en priorité depuis l’EAR. L’application est « scoped » et le Java2ParentDelegation est off.

Pour mettre en œuvre la solution n°3 concernant à « scoper » l’application, il est nécessaire de configurer le chargement des classes de JBoss . Une description détaillée de son fonctionnement est disponible sur la page JBossClassLoadingUseCases du wiki de JBoss.
Dans notre cas, La configuration des classes loaders nécessaire est deployment scoped et Java2ParentDelegation désactivé. Cette configuration est représentée par la figure ci-contre.

Cette configuration présente les 2 avantages suivants :

  1. Les JARs embarqués dans l’EAR priment sur celles fournies par JBoss 5.1 et le JRE.
  2. Chaque application déployée sur le même serveur possède son propre UnifiedLoaderRepository (ULR). Le chargement des classes est isolé et n’interfère pas. Elles sont également isolées du chargement des applications tierces (ex: jmx-console et admin-console).

La configuration du fichier jboss-app.xml à déposer dans le répertoire META-INF de l’EAR est décrite sur la page ClassLoadingConfiguration du wiki JBoss. En voici un exemple :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-app PUBLIC
  "-//JBoss//DTD Java EE Application 5.0//EN"
  "http://www.jboss.org/j2ee/dtd/jboss-app_5_0.dtd">
<jboss-app>
    <loader-repository>com.javaetmoi:archive=monapplication-ear
        <loader-repository-config>java2ParentDelegation=false</loader-repository-config>
    </loader-repository>
</jboss-app>

Configuration maven

Le plugin maven-ear-plugin permet de générer ce fichier jboss-app.xml :

<!-- Generation du classPath dans le Manifest de l'EAR, paramétrage du classloader -->
<!-- et recopie centralisée des JARs des WARs dans le répertoire lib -->
<plugin>
  <artifactId>maven-ear-plugin</artifactId>
  <configuration>
    <version>5</version>
    <defaultJavaBundleDir>lib/</defaultJavaBundleDir>
    <applicationXml>${project.build.directory}/application.xml</applicationXml>
    <archive>
      <manifest>
        <addClasspath>true</addClasspath>
      </manifest>
    </archive>
    <!-- Génère le fichier jboss-app.xml se trouvant dans le sous-répertoire META-INF de l'EAR -->
     <jboss>
      <version>5</version>
      <!-- Fait en sorte que que l'application ait son propre UnifiedLoaderRepository (ULR) -->
      <!-- Le déploiement de l'EAR est dit "scoped" -->
      <loader-repository>com.javaetmoi:archive=${project.artifactId}</loader-repository>
      <!-- Le flag Java2ParentDelegation est désactivé afin que les classes soient en priorité 
        chargées à partir des libs de l'EAR -->
      <loader-repository-config>java2ParentDelegation=false</loader-repository-config>
    </jboss>
  </configuration>
</plugin>

Conclusion

Scoper l’EAR déployé sur JBoss vous laisse la possibilité de choisir la version des frameworks que vous souhaitez et de ne pas vous le laisser imposer. Vous pourrez ainsi utiliser Hibernate comme implémentation de JPA 2 (en mode embarqué avec le support offert par Spring), Hibernate Validator 4 comme implémentation de la JSR 303 Bean Validation ou même SLF4J 1.6 et Logback 1.0.9 pour gérer vos traces.
A des fins de debug, tout client JMX permet de consulter les classes disponibles dans l’UnifiedLoaderRepository.
Enfin, pour un réglage plus fin du classloader, et bien que je n’ai pas eu besoin d‘y recourir, une configuration avancée du jboss-classloading.xml est a priori possible.

Références :

  1. JBossClassLoadingUseCases : https://community.jboss.org/wiki/JBossClassLoadingUseCases
  2. ClassLoadingConfiguration : https://community.jboss.org/wiki/ClassLoadingConfiguration
  3. A Look Inside JBoss Microcontainer’s ClassLoading Layer : http://java.dzone.com/articles/jboss-microcontainer-classloading
  4. Demystifying the JBoss5 jboss-classloading.xml file  : http://phytodata.wordpress.com/2010/10/21/demystifying-the-jboss5-jboss-classloading-xml-file/
]]>
https://javaetmoi.com/2013/01/isoler-classloader-ear-jboss/feed/ 0