Comment puis-je écrire un Java EE / EJB Singleton?

Il y a un jour, ma demande était un seul fichier EAR, contenant un fichier WAR, un fichier JAR EJB et quelques fichiers JAR d’utilitaire. J’ai eu une classe de singleton POJO dans l’un de ces fichiers utilitaires, cela a fonctionné, et tout allait bien avec le monde:

EAR |--- WAR |--- EJB JAR |--- Util 1 JAR |--- Util 2 JAR |--- etc. 

Ensuite, j’ai créé un deuxième WAR et découvert (à la dure) que chaque WAR a son propre ClassLoader, de sorte que chaque WAR voit un singleton différent et que tout se décompose. Ce n’est pas si bon.

 EAR |--- WAR 1 |--- WAR 2 |--- EJB JAR |--- Util 1 JAR |--- Util 2 JAR |--- etc. 

Je cherche donc un moyen de créer un object singleton Java qui fonctionnera sur plusieurs fichiers WAR (sur des chargeurs de classe?). L’annotation @Singleton EJB semblait assez prometteuse jusqu’à ce que je découvre que JBoss 5.1 ne semble pas prendre en charge cette annotation (qui a été ajoutée dans EJB 3.1). Ai-je @Singleton quelque chose – puis-je utiliser @Singleton avec JBoss 5.1? La mise à niveau vers JBoss AS 6 n’est pas une option pour le moment.

Alternativement, je serais tout aussi heureux de ne pas avoir à utiliser EJB pour implémenter mon singleton. Que puis-je faire pour résoudre ce problème? Fondamentalement, j’ai besoin d’un crochet semi-applicatif * pour tout un tas d’autres objects, tels que diverses données en cache et les informations de configuration d’application. En dernier recours, j’ai déjà envisagé de fusionner mes deux guerres en un seul, mais ce serait plutôt terrible.

* Signification: disponible essentiellement n’importe où au-dessus d’une certaine couche; pour le moment, principalement dans mes WAR – la vue et le contrôleur (dans un sens vague).

Edit: Je devrais vraiment l’appeler Java EE plutôt que J2EE, n’est-ce pas?


Edit 2: Un grand merci encore à @Yishai pour son aide. Après quelques essais et erreurs, il semble que j’ai compris comment utiliser un seul ClassLoader sur des WAR sous JBoss 5. Je détaille cela ci-dessous pour mon propre intérêt, et j’espère que d’autres le trouveront également utile.

NB ceci est assez différent de faire cela sous JBoss 4 (voir la réponse de Yishai ou mes liens ci-dessous).

Au lieu d’écrire un jboss-web.xml pour chaque jboss-web.xml WAR et un jboss.xml pour un jboss.xml EJB-JAR, placez un fichier jboss-classloading.xml dans chaque fichier WAR, au même emplacement que le fichier DD ( web.xml ). Le contenu de jboss-classloading.xml devrait être:

    

Cela découle de la CW de JBoss ici , alors que ce qui fonctionne (je pense) pour JBoss 4.x est décrit ici . Informations plus générales sur JBoss classload (ing / ers):

  • Vue d’ensemble
  • Histoire
  • Cas d’utilisation

Autant que je sache, les documents wiki de la communauté JBoss manquent beaucoup pour JBoss 5 par rapport à JBoss 4.

Bien que la spécification EJB3.1 introduise singleton et que votre version de JBoss ne la prenne pas en charge, vous pouvez utiliser l’annotation JBoss @Service pour créer un singleton. Instructions ici . En outre, il semble que JBoss soit configuré pour isoler vos jar ejb et vos guerres les unes des autres. Tu n’as pas à faire ça. Vous pouvez consulter la balise loader-repository dans les fichiers xml spécifiques à jboss afin que votre oreille entière partage un chargeur de classe (ou peut-être qu’au moins les deux guerres partagent un chargeur de classe).

Ceci étant dit, je suis d’accord avec @duffymo, qu’un singleton qui partage l’état entre les deux guerres est une idée que vous devriez marcher, sinon fuir.

Edit: En ce qui concerne les singletons, je vous suggère de regarder des questions comme celle-ci (qui présente également un bel équilibre dans les commentaires).

L’idée de conserver en mémoire cache l’état d’un object est acceptable, en particulier avec EJB3 où vous pouvez injecter votre état au lieu de le référencer statiquement (si vous utilisez l’annotation @Service, vous voulez l’annotation spécifique à @Depends JBoss). Cela étant dit, si vous utilisiez un “bon” singleton ici, je m’attendrais à ce que votre seul problème avec le fait que vos fichiers WAR ont deux chargeurs de classe distincts est l’encombrement supplémentaire de la mémoire. Sinon, vous vous retrouvez dans la zone problématique des singletons (où ils doivent être initialisés pour être utilisés, tout ce qui les utilise doit garantir leur initialisation en premier, et bien sûr tout le code est fortement associé à leur initialisation).

Là où les singletons sont vraiment mauvais, ils stockent l’état de sorte qu’une classe puisse changer d’état et qu’une autre classe le récupère. C’est fondamentalement un non-non dans les EJB jusqu’à la version 3.1, et même dans ce cas, cela crée beaucoup de problèmes de simultanéité.

Éditer (plus loin): Vous voulez donc aller avec le référentiel de chargeur de classe. J’utilise JBoss 4.2.3, donc je ne connais pas forcément tous les tenants et les aboutissants de JBoss5 (qui a réécrit son chargeur de classes bien qu’ils disent qu’il est presque totalement compatible avec les versions antérieures). Cependant, dans 4.2.x, votre configuration ne provoque pas problèmes car toutes les oreilles déployées sur le serveur partagent le même chargeur de classe (le “chargeur de classe unifié”). Ce que je soupçonne, c’est que le serveur sur lequel vous déployez a la configuration différemment. Je ne sais donc pas comment interagir avec elle, mais vous devez append un fichier appelé jboss-app.xml dans votre oreille (dans le même emplacement que le fichier application.xml) qui ressemble à ceci:

      com.yourcomany:archive=yourear   

C’est pour JBoss 4.2. 5.1 a le même type de balise, voici le xsd . Il a le même concept chargeur-référentiel.

Cela devrait être ça. C’est-à-dire que tant que votre ejb-jar, votre guerre, etc. ne l’a pas, ils n’en ont pas besoin. Cependant, vos guerres (dans jboss-web.xml – au même endroit que le web.xml) peuvent nécessiter la même chose. Dans ce cas, tant que vous appelez le référentiel exactement de la même manière (si je comprends bien, je n’ai jamais essayé moi-même), ils partageront le même chargeur de classes. Il en va de même pour les EJB tels que configurés dans le fichier jboss.xml qui se trouve au même emplacement que le fichier ejb.xml.

Cela pourrait le rendre un peu plus clair.

Je configurerais un pool d’objects distinct sur votre serveur d’applications afin qu’il ne contienne qu’une seule instance.

Pourquoi vous voudriez faire ceci est la vraie question. On dirait que toutes vos applications seront couplées de cette façon. Et Google élimine singleton de ses applications. Pourquoi voyez-vous la nécessité de le ramener?

Vous pouvez utiliser un MBean et le lier à JNDI, puis le récupérer où que vous souhaitiez l’utiliser.

Le MBean peut être déployé dans un fichier .sar

Si vous utilisez Java EE 6, il prend en charge les EJB singleton.

Si cela est pratique, prenez simplement la classe qui a le singleton, mettez-la dans un fichier JAR, sortez le fichier JAR de l’EAR et ajoutez-le au jargon du chargeur de classes JBoss (via le chemin de classe système ou un répertoire lib). Cela place la classe dans un chargeur de classe unique partagé par les deux WAR.

Le singleton ne pourra rien voir dans les fichiers WAR, etc. de vos applications, car ils se trouvent dans un chargeur de classe inférieur.

Cependant, rien ne vous empêche d’injecter dans le singleton (au démarrage du serveur) une classe fabrique, qui provient des fichiers WARs et autres, et CETTE classe a access à toutes les classes d’applications. Cela rend le singleton plus d’un simple conteneur.

Mais c’est simple à faire.

En outre, si vous procédez ainsi, assurez-vous, lorsque vous fermez votre application, que toutes les instances détenues par ce singleton sont libérées. Aucune référence de classe par le singleton ne sera pas atsortingbuée à GC’d lorsque vous annulez le déploiement de l’application. Ainsi, si vous avez une référence à votre application stockée dans le singleton, le serveur contient une référence au chargeur de classe singletons, ce chargeur de classe contient une référence à votre classe singleton, qui contient une référence à votre classe apps, qui contient une référence au apps CLASSLOADER, et THAT contient une référence à toutes les classes de votre application. Pas un beau bordel à laisser derrière.