Tutoriel pour la mise en place de traitements concurrentiels en JEE

Mith Moukala

Ce tutoriel s'intéresse aux traitements concurrents et asynchrones dans un programme Java/JEE, de la version JEE 1.4 à la JEE 7.

Pour réagir à ce tutoriel, un espace de dialogue vous est proposé sur le forum : Commentez

- Titre : Tutoriel pour la mise en place de traitements concurrentiels en JEE

- Auteur : Mith Moukala

- Parution : 9 juin 2015

- Licence : Le contenu de cet article est rédigé par SQLI et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 3.0 non transposé. Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright ® 2013 Developpez.com.

Sommaire

- I. Introduction

- II. Application Java standard edition

- III. Application java entreprise edition

- IV. Bonnes pratiques et solutions pour les applications JEE

- IV-A. Cas JEE 6 ou inférieur (à partir de la version J2EE 1.4)

- IV-A-1. Mise en place sur un serveur de conteneur JSP/Servlet Tomcat

- IV-A-2. Mise en place sur un serveur certifié JEE 6 Oracle GlassFish (ogs)

- IV-A-3. Mise en place sur un serveur Rational Application Development (RAD)

- IV-A-3-a. LE CAS JEE 7

- IV-A-4. Mise en place sur un serveur certifié JEE 7 Oracle Glassfish (OGS)

- V. Conclusion

- VI. Remerciements

- VII. Références

- VIII. Annexe : structure des packages projets pour les sources

I - Introduction

La gestion de traitements en parallèle et de manière asynchrone est une problématique récurrente dans les applications d'entreprise, plus particulièrement dans le contexte d'un développement spécifique Java/JEE.

L'objectif que nous nous fixons dans le cadre de cette étude est de faire des traitements concurrents et asynchrones dans un programme Java/JEE, de la version JEE 1.4 à la JEE 7. Pourquoi J2EE 1.4 ? Il faut bien se fixer un intervalle de champ d'investigation, et vous comprendrez par la suite pourquoi le choix de cette version de départ.

Cas d'étude COMPTAGE : pour mettre en pratique notre étude, nous allons le faire au travers d'une application métier qui fait des traitements de comptage avec un nom de comptage et une taille maximale de fin comptage.

II - Application Java standard edition

Dans une application Java standard hors contexte JEE, la solution standard depuis le JSE 5 (JSR 176) est d'utiliser l'API java.util.concurrent.

Rappel rapide des composants majeurs de ce package :

- BlockingQueue<E> : interface collection qui remplit un rôle de Queue, sur les objets utilisés par les Thread en cours de fonctionnement ;

- Executor : objet interface qui permet l'exécution des tâches Runnable ;

- ExecutorService : objet de type Executor, avec des méthodes qui permettent de gérer les tâches Runnable en cours d'exécution ;

- ScheduledExecutorService: objet de type ExecutorService, avec un Timer qui peut être renseigné, pour une exécution de Thread à une période bien spécifiée ;

- ThreadFactory : objet qui permet de créer des Threads à la demande.

Nous allons appliquer cette approche sur notre cas d'étude COMPTAGE.

Diagramme de classes : qui représente une conception des classes de notre cas d'étude selon l'approche Java standard.

- IBusinessTraitment : interface qui expose les règles métiers ;

- BusinessTraitement : cette classe implémente l'interface IbusinessTraitement et applique les règles et le traitement métier ;

- IServiceTraitement : interface qui réalise le traitement métier. Elle étend l'interface Runnable et expose le traitement sous forme de service métier ;

- ServiceTraiment : cette classe implémente l'interface IServiceTraitement. Agrège la couche Business (IBusinessTraiement) et implémente la méthode run() contenant le code à paralléliser et synchroniser (si besoin selon les exigences métiers) ;

- ManagerServiceTraiment : classe qui utilise l'interface ExecutorService, qui va exécuter les tâches dans la méthode startTraitement(). La méthode main() est la méthode d'exécution de ce cas d'exemple.

III - Application java entreprise edition

Dans une application J2ee standard (version inférieure à la J2EE 7), il est constaté que la plupart des applications JEE qui implémentent un mécanisme de traitement avec du parallélisme asynchrone intègrent généralement de manière brute la classe Java thread standard du JRE : une pratique qu'il faudrait bannir.

Dans un conteneur JEE, la gestion des threads est difficile et est susceptible d'interférer de façon imprévisible avec le comportement du conteneur. Même sans interférer avec le conteneur J2EE, la gestion manuelle des threads conduit généralement à des bugs qui sont difficiles à détecter et à diagnostiquer.

Nous allons nous interdire la pratique d'une intégration d'un thread dans un conteneur JEE, pour les raisons suivantes :

- De cycle de vie : le cycle du thread est associé à la JVM et n'est pas managé par le conteneur JEE (l'arrêt du contexte JEE ne garantit pas l'arrêt du thread lancé à partir de ce contexte JEE).

- De partage de contexte : le thread n'a pas accès aux objets du contexte du conteneur JEE (ex : les sources de données et les objets JNDI du conteneur JEE).

- De synchronisation : un thread qui est lancé a un fonctionnement asynchrone vis-à-vis du conteneur JEE. En conséquence, aucun objet du conteneur JEE ne peut se synchroniser avec un thread en cours de fonctionnement.

- Nous n'allons pas énumérer toutes les hypothèses qui vont à l'encontre de l'utilisation des threads dans un conteneur J2EE. Nous allons plutôt nous pencher sur les bonnes pratiques à appliquer.

IV - Bonnes pratiques et solutions pour les applications JEE

IV-A - Cas JEE 6 ou inférieur (à partir de la version J2EE 1.4)

La Java Specification Requests (JSR) 316 qui régit la spécification de la norme JEE 6 n'intègre aucune JSR sur le traitement concurrentiel ou sur le Work Manager. Mais néanmoins, il est possible de faire de l'asynchrone avec l'EJB 3.1. Cet aspect de l'asynchrone en JEE 6 répond partiellement à notre problématique et ne s'applique pas sur des versions inférieures à JEE6.

IBM et BEA Systems ont initié une JSR 237 (publiée le 15/12/2003) pour ce faire ; cette spécification est connue sous le nom d'API Work Manager. Cette JSR a été maintes fois proposée, mais malheureusement n'a jamais été retenue. Les contributeurs (IBM et BEA) ont introduit cette implémentation dans leur serveur JEE respectif (depuis la version J2EE 1.4 source IBM). La JSR 237 a été retirée le 15/06/2008, ce qui est logique vu l'arrivée de la JSR 236 incluse dans la JSR 342 de JEE 7.

IV-A-1 - Mise en place sur un serveur de conteneur JSP/Servlet Tomcat

Foo-CommonJ est une implémentation Open Source de la JSR 237 : Work Manager for Application Server. Le but de cette implémentation est de permettre aux serveurs d'applications Open Source (Tomcat, JBoss, WASCE…) de faire du Work Manager avec aussi une capacité de Timer (exécution à des périodes de temps spécifiées). Tomcat n'est pas un serveur JEE, mais comme l'API Foo-CommonJ ne dépend d'aucune capacité JEE, elle peut être mise en place sur le serveur de Conteneur JSP/Servlet.

Pour notre exemple de mise en place, nous allons utiliser la version de l'API Foo-CommonJ compatible Java 1.5 et supérieure, ce qui nous oblige à utiliser une version de Tomcat niveau 6 minimum.

La mise en pratique du mécanisme de Work Manager dans un Serveur Open Source compatible Java 1.5 se fait en trois étapes :

1. Déclaration d'une ressource JNDI pour le work WorkManager
Dans mon cas, j'ai utilisé le serveur Tomcat, la déclaration du contexte JNDI se fait dans le fichier context.xml, il faut rajouter les lignes suivantes :

<Resource name="wm/CompteurWM"
auth="Container"
type="commonj.work.WorkManager"
factory="de.myfoo.commonj.work.FooWorkManagerFactory"
maxThreads="5" />

2. Déclaration de la ressource référencée dans web.xml (resource-ref) : pour les applications utilisant une version inférieure à l'API Servlet 3.0
Dans le fichier de description web.xml, il faut rajouter la référence à la ressource configurée dans le contexte Tomcat.

<resource-ref>
<res-ref-name>wm/CompteurWM</res-ref-name>
<res-type>commonj.work.WorkManager</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>

3. Initialisation du WorkManager

L'initialisation du workmanager se fait par recherche dans le contexte JNDI, en faisant un lookup sur le nom JNDI déclaré dans le contexte du serveur Tomcat pour les versions inférieures à la Servlet 3.0 (sinon une injection du type @Resource (name=« wm/CompteurWM » permet d'initialiser la ressource).

InitialContext ctx = new InitialContext();
WorkManager wkMgr = (WorkManager) ctx.lookup("java:comp/env/wm/CompteurWM");

Diagramme de classes : qui représente une conception des classes de notre cas d'étude selon l'approche J2ee. Les impacts sont les suivants :

- IServiceTraitementWeb : interface qui réalise le traitement métier. Elle étend l'interface commonj.work.Work . Elle remplace l'interface (du cas Java SE) : IServiceTraitement ;

- ServiceTraimentWeb : cette classe implémente l'interface IServiceTraitementWeb. Agrège la couche Business (IBusinessTraiement) et implémente la méthode run() contenant le code à paralléliser et synchroniser (si besoin selon les exigences métiers). Deux méthodes doivent être implémentées isDeamon() et release() ;

- ManagerServiceTraimentWeb : classe qui utilise l'interface IServiceTraitementWeb et instancie le commonj.work.WorkManager, cette dernière a la responsabilité de lancer les traitements des Work(s) dans la méthode startTraiement() : méthode d'entrée d'exécution de ce cas d'exemple ;

- ContextListenerWorkManager : classe JEE qui implémente l'interface javax.servlet.ServletContextListener. Elle utilise la classe ManagerServiceTraitementWeb, pour lancer le traitement au déploiement de l'application ;

- ServletWorkManager : classe JEE qui implémente la classe abstraite javax.servlet.HttpServlet. Elle utilise la classe ManagerServiceTraitementWeb, pour lancer le traitement à travers la requête HTTP :

[host :port/wkm-web-simple/workManager]

IV-A-2 - Mise en place sur un serveur certifié JEE 6 Oracle GlassFish (ogs)

Le 20 avril 2009, Oracle Corporation rachète Sun Microsystems propriétaire de Java et devient à cette occasion propriétaire de Java et du serveur JEE Open Source GlassFish.

Nous allons utiliser la version 3.1 du serveur OGS, pour mettre en œuvre notre cas d'étude, faire du Concurrency et de l'asynchrone sur une application JEE 6. Le serveur OGS 3.1 est certifié JEE 6.

La mise en place sur le serveur OGS 3.1 se fait en trois étapes :

- Déclaration d'une ressource JNDI pour le WorkManager : dans mon cas, j'ai utilisé l'interface IHM (Common Task/Domain/JNDI/Custom resources/).

- Déclaration de la ressource référencée dans web.xml (resource-ref) : idem à Tomcat (obligatoire seulement pour les applications avec une version JEE inférieure à 6).

- Initialisation du WorkManager : idem à Tomcat (par injection pour les applications JEE 6 et supérieure).

Diagramme de classe : qui représente une conception des classes de notre cas d'étude selon l'approche J2EE 6. Les impacts sont les suivants.

Nous n'allons énumérer que les mises à jour du diagramme de classe en partant de celui du chapitre (2.2 avec Tomcat), modifications générées par le passage en structure EAR du projet.

- ServiceMgrTraiment: interface qui représente EJB local qui expose le service ;

- ServiceMgrTraimentBean : classe EJB Stateless qui implémente l'interface IServiceMgrTraiment et qui utilise l'interface IServiceTraitementWeb pour réaliser le traitement. Ce bean remplace la classe ManagerServiceTraimentWeb ;

- ContextListenerWorkManager : utilise l'EJB local (IServiceMgrTraiment) pour initier les traitements ;

- ServletWorkManager : utilise l'EJB local (IServiceMgrTraiment) pour initier les traitements.

IV-A-3 - Mise en place sur un serveur Rational Application Development (RAD)

IBM et BEA sont les auteurs de la JSR 237, IBM utilise une implémentation plus spécifique à Websphère (version RAD 7).

La mise en pratique du mécanisme de WorkManager dans RAD se fait en trois étapes.

- La déclaration de la ressource WorkManager sur nœud serveur actif (Ressources > Beans asynchrones > Gestionnaire des travaux).

- La déclaration de la ressource référencée dans web.xml (resource-ref) : idem à Tomcat.

- L'initialisation du WorkManager : idem à Tomcat (pour les applications JEE 6 initialisation par injection de la ressource par son nom JNDI).

Il faut noter que cette implémentation est bien gérée au niveau du serveur RAD (version Java EE 6 et inférieure). La documentation de l'éditeur est plus adéquate pour implémenter cette solution non standard JEE (avant la version 7).

IV-A-3-a - LE CAS JEE 7

La JSR 342 (publiée le 29/04/2013) qui régit la spécification de la norme JEE 7, intègre la JSR 236 Concurrency Utilities for JEE. Cette JSR régit les pratiques de mise en place d'un traitement concurrentiel et asynchrone dans les applications JEE.

Un focus sur la JSR 236 : Java EE (dans sa version 7) vient de se doter d'une capacité à faire du concurrentiel et de l'asynchrone. En lisant la spécification JSR 236, cette capacité est portée sur le conteneur Web (JavaServer Faces et Servlet) et sur le conteneur EJB (spécification EJB 3.2).

Nous allons faire un focus sur les composants majeurs de cette implémentation :

ManagedExecutorService : objet qui permet l'exécution des threads et gère la file d'exécution, le contexte du conteneur est partagé dans les threads exécutés ;

ManagedScheduledExecutorService : objet qui permet l'exécution des threads, avec un timer qui peut être renseigné, pour une exécution de thread à une période bien spécifiée ;

ContextService : c'est l'objet qui porte le contexte du conteneur d'exécution des threads, ce contexte est partagé par les objets qui exécutent les threads ;

ManagedThreadFactory : cet objet permet de créer des objets qui managent les threads, il permet la création d'objets plus spécialisés dans le contexte du conteneur d'exécution de thread.

IV-A-4 - Mise en place sur un serveur certifié JEE 7 Oracle Glassfish (OGS)

Nous allons utiliser la version 4.0 du serveur OGS, pour mettre en œuvre notre cas d'étude, faire du Concurrency et l'asynchrone sur une application JEE 7. Le serveur OGS 4.0 est certifié Jee 7.

La mise en place sur le serveur OGS 4.0, se fait en deux étapes :

1. Déclaration d'une Ressource Concurrency JNDI, dans notre cas d'exemple nous allons choisir la ressource de type ManagedSexecutorServices

Dans mon cas j'ai utilisé l'interface IHM

(Common Task/Resources/Concurrent Ressources/Managed Executor Services), puis renseigné les valeurs suivantes :

JNDI name : xxxxx
Thread Priority: 10 (valeur conseillée)
Core Size: 2 (valeur conseillée)
Maximum Pool Size: 5 (valeur conseillée)
Task Queue Capacity: 2 (valeur conseillée)

1. Initialisation du ManagedExecutorService dans le code applicatif par injection :

-@Resource(name=ConstantesWeb.JNDI_NAME_WKM)ManagedExecutorService managedExecutorService;

—Diagramme de classes : qui représente une conception des classes de notre cas d'étude selon l'approche J2EE 7. Les impacts sont les suivants. Nous n'allons énumérer que les mises à jour du diagramme de classes en partant de celui du chapitre (2.3 OGS 3.1), modification générée par le passage de JEE 6 à JEE 7.

- IServiceMgrTraitement: interface qui représente EJB local qui expose le service.

- ServiceMgrTraitementBean : classe EJB Stateless qui implémente l'interface IServiceMgrTraitement et qui utilise l'interface IServiceTraitement pour réaliser le traitement. Ce bean utilise l'interface ManagedExcecutorService pour exécuter les traitements thread.

V - Conclusion

Le code source est disponible à cette adresse : source_workshop_wm-cu_1.0.zip du cas pratique.

Nous pouvons conclure que notre objectif (faire du traitement concurrentiel et asynchrone dans un contexte Java EE de la version 1.4 à 7) est atteint. Notre solution respecte les bonnes pratiques qu'impose la norme Java EE, tout en ayant une conception architecturale qui s'imbrique parfaitement avec les différentes versions de Java EE ciblées pour l'étude.

Il faut noter que l'API Commonj ne fait pas partie de la norme JEE. Donc, dans le cas d'un usage de cette API, il faudrait bien structurer votre conception technique, pour avoir la possibilité de détruire tous les Threads en cours à l'arrêt du contexte applicatif et pour éviter tout conflit au redémarrage (ou rechargement) de cette dernière (voir un exemple dans le ContexteListenerWorkManager du cas pratique).

Une préconisation (voire un conseil) : ne jamais utiliser l'API Commonj pour une version Java EE 7, vu que ce dernier né de la norme JEE fournit un mécanisme standard porté par la JSR 236.

Ce tableau représente les réponses des objectifs fixés : faire du traitement concurrentiel et asynchrone en JEE.

VI - Remerciements

Cet article a été publié avec l'aimable autorisation de SQLI qui est le partenaire de référence des entreprises dans la définition, la mise en œuvre et le pilotage de leur transformation digitale.

Nous tenons à remercier ced pour la relecture orthographique et milkoseck pour la mise au gabarit.

VII - Références

Les sources suivantes m'ont permis de réaliser mon travail de recherche :

- JSR 176 : J2SETM 5.0 (Tiger) Release Contents (http://jcp.org/en/jsr/detail?id=176) ;

- JSR 236: Concurrency Utilities for JavaTM EE (http://jcp.org/en/jsr/detail?id=236) ;

- JSR 237: Work Manager for Application Servers (http://jcp.org/en/jsr/detail?id=237) ;

- SR 316: JavaTM Platform, Enterprise Edition 6 (Java EE 6) Specification (http://jcp.org/en/jsr/detail?id=316)

- JSR 342: JavaTM Platform, Enterprise Edition 7 (Java EE 7) Specification (http://jcp.org/en/jsr/detail?id=342)

- WebSphere Application Server Version 6.1 Information Center (http://pic.dhe.ibm.com/infocenter/wasinfo/v6r1/index.jsp)

- CommonJ − JSR 237 Timer & WorkManager (http://commonj.myfoo.de/index.shtml).

VIII - Annexe : structure des packages projets pour les sources

Projet (wkm-jar) : projet Java standard pour le cas pratique (pas de structure particulière, tout est en Java standard).

Projet (wkm-web-simple) : projet web dynamique qui est déployé sur Tomcat, pour le cas pratique, qui embarque deux composants sous forme de librairies (api-jsr237-commj-jar et wkm-jar).

Projet (wkm-jee6-ear) : projet EAR pour le cas pratique en JEE 6, contenant les composants (wkm-jee6-web : partie web et wkm-jee6-ejb : pour la partie EJB).

Projet (wkm-jee7-ear) : projet EAR pour le cas pratique en JEE 7, contenant les composants (wkm-jee7-web : partie web et wkm-jee7-ejb : pour la partie EJB).