2010-05-04 16 views
7

Un giorno fa la mia applicazione era un EAR, contenente un WAR, un JAR EJB e un paio di file JAR di utilità. Ho avuto una classe POJO Singleton in uno di quei file di utilità, ha funzionato, e tutto andava bene con il mondo:Come scrivere un Java Singleton EE/EJB Java?

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

Poi ho creato una seconda guerra e scoperto (nel modo più duro) che ogni guerra ha la sua proprio ClassLoader, quindi ogni WAR vede un singleton diverso, e le cose si interrompono da lì. Non è così buono

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

Quindi, sto cercando un modo per creare un oggetto Singleton Java che funziona attraverso guerre (in tutto ClassLoader?). L'annotazione EJB @Singleton sembrava abbastanza promettente fino a quando ho scoperto che JBoss 5.1 non sembra supportare quell'annotazione (che è stata aggiunta come parte di EJB 3.1). Mi sono perso qualcosa: posso usare @Singleton con JBoss 5.1? L'aggiornamento a JBoss AS 6 non è un'opzione al momento.

In alternativa, sarei altrettanto felice di non dover utilizzare EJB per implementare il mio singleton. Cos'altro posso fare per risolvere questo problema? Fondamentalmente, ho bisogno di un hook semi-application * in un gruppo di altri oggetti, come i vari dati memorizzati nella cache, e le informazioni di configurazione dell'app. Come ultima risorsa, ho già pensato di unire le mie due WAR in una, ma sarebbe stato piuttosto infernale.

* Significato: disponibile praticamente ovunque sopra un determinato livello; per ora, per lo più nelle mie WAR, la vista e il controller (in senso sciolto).

Modifica: Dovrei davvero chiamarlo Java EE piuttosto che J2EE, non dovrei?


Edit 2: Molte grazie ancora a @Yishai per tutto l'aiuto. Dopo un po 'di prove ed errori sembra che io abbia capito come usare un singolo ClassLoader su WARs sotto JBoss 5. Sto descrivendo questo sotto per il mio bene, e spero che anche altri trovino questo utile.

N.B. questo è piuttosto diverso dal fare questo sotto JBoss 4 (vedi la risposta di Yishai o i miei link sotto).

Invece di scrivere un jboss-web.xml per ogni guerra e un jboss.xml per l'orecchio EJB-JAR, mettere un file jboss-classloading.xml in ogni guerra, nella stessa posizione del DD (web.xml). Il contenuto di jboss-classloading.xml dovrebbero essere:

<?xml version="1.0" encoding="UTF-8"?> 
<classloading 
    xmlns="urn:jboss:classloading:1.0" 
    name="mywar.war" 
    domain="DefaultDomain" 
    parent-domain="Ignored" 
    export-all="NON_EMPTY" 
    import-all="true"> 
</classloading> 

Ciò deriva dal JBoss CW here, mentre ciò che (credo) lavora per JBoss 4.x è descritto here. informazioni più generali su JBoss classload (ING/ERS):

Come meglio posso dire, la documentazione della comunità wiki JBoss sono piuttosto carente per JBoss 5 in confronto a JBoss 4.

+0

@Bears, re: la modifica: solo se lavori nel reparto marketing di Oracle. – Yishai

risposta

11

Anche se la specifica EJB3.1 introduce Singleton e la versione di JBoss non lo supporta, è possibile utilizzare l'annotazione JBoss @Service per creare un Singleton. Istruzioni here. Inoltre, sembra che JBoss sia configurato per isolare i tuoi ejb e le tue guerre l'una dall'altra. Non devi farlo. Puoi guardare il tag loader-repository nei file xml specifici di jboss in modo che l'intero orecchio condivida un classloader (o forse che almeno le due guerre condividano un classloader).

Detto questo, sono d'accordo con @duffymo, che un singleton che condivide lo stato tra le due guerre è un'idea che dovresti camminare, se non fuggire.

Modifica: Per quanto riguarda i singleton, suggerisco di guardare le domande come this one (che ha anche un buon bilanciamento nei commenti). L'idea di avere un oggetto in attesa di memorizzazione nella cache è ok, specialmente con EJB3 in cui è possibile iniettare lo stato invece di fare riferimento statico (se si utilizza l'annotazione @Service, si desidera che @Depends JBoss annotazione specifica). Detto questo, se si stesse utilizzando un singleton "appropriato" qui, allora mi aspetto che il tuo unico problema con il fatto che i tuoi WAR abbiano due distinti caricatori di classi è l'ingombro di memoria extra. Altrimenti ci si trova nell'area problematica dei singleton (dove devono essere inizializzati per essere usati, tutto ciò che li usa deve garantire che siano inizializzati per primi, e ovviamente tutto il codice si accoppia molto con il loro essere inizializzati).

Dove singleton sono davvero molto male è dove memorizzano lo stato in modo che una classe possa cambiare stato e un'altra classe la preleva. È fondamentalmente un no-no in EJB fino alla versione 3.1, e anche in questo caso genera molti problemi di concorrenza.

Modifica (ulteriori): Quindi si desidera andare con il repository classloader. Io uso JBoss 4.2.3, quindi non conosco necessariamente tutti i dettagli di JBoss5 (che ha riscritto il suo classloader anche se dicono che è quasi completamente retrocompatibile), tuttavia in 4.2.x la configurazione predefinita non causa problemi perché tutte le orecchie distribuite sul server condividono lo stesso classloader (il "classloader unificato"). Quello che sospetto è che il server su cui si sta distribuendo abbia la configurazione in modo diverso, quindi non sono sicuro di come interagire con esso, ma quello che devi fare è aggiungere un file chiamato jboss-app.xml nel tuo orecchio (in la stessa posizione di application.xml) è simile a questa:

<?xml version="1.0"?> 
<!DOCTYPE jboss-app PUBLIC "-//JBoss//DTD J2EE Application 4.2//EN" 
     "http://www.jboss.org/j2ee/dtd/jboss-app_4_2.dtd"> 
<jboss-app> 
     <loader-repository> 
     com.yourcomany:archive=yourear 
     </loader-repository> 
</jboss-app> 

Vale per JBoss 4.2. 5.1 ha lo stesso tipo di tag, ecco lo xsd. Ha lo stesso concetto di caricatore-repository.

Che dovrebbe essere. Cioè, finché il tuo ejb-jar, la guerra, ecc. Non ce l'hanno, allora non ne hanno bisogno. Tuttavia, le tue guerre (in jboss-web.xml - stessa posizione del web.xml) potrebbero aver bisogno della stessa cosa. In questo caso, se si identifica il repository esattamente nello stesso modo (se ho capito bene - non l'ho mai provato personalmente) condivideranno lo stesso classloader. Lo stesso vale per EJB configurato nel jboss.xml che si trova nella stessa posizione di ejb.xml.

This potrebbe renderlo un po 'più chiaro.

+0

Sono decisamente aperto a essere convinto su questo punto, ma sono un po 'noob quando si tratta di J2EE (non ho un addestramento formale e in pratica ho appena fatto Apprendimento JIT da quando ho iniziato a lavorare sulle app J2EE della mia azienda). Quindi: perché è sbagliato condividere lo stato tra WAR? Per come la vedo io, ogni WAR è solo una visione diversa della mia applicazione. Uno è per gli utenti di tutti i giorni e l'altro è per le funzioni di amministrazione delle app (come creare/modificare/eliminare utenti, autorizzazioni di accesso, ecc.). –

+0

Ho ampliato la mia risposta per discutere di Singletons, ma i problemi con loro non sono specifici per J2EE (anche se le applicazioni di livello J2EE hanno alcuni problemi quando si parla di clustering ecc.). – Yishai

+0

Dopo essersi consultato con i superiori, l'obiettivo per ora è solo quello di far funzionare le cose, e preoccuparsi di Doing It Right (in seguito, il prossimo progetto). Proverò a utilizzare un classloader per l'intero EAR per ridurre al minimo il codice che devo modificare, ma sembra che manchino parole [repository del caricatore] (http://community.jboss.org/wiki/classloadingconfiguration) link. Qualche idea su dove posso trovare un documento completo su cosa devo fare? –

1

Avrei configurato un pool di oggetti separato sul server dell'app in modo che contenesse solo la singola istanza.

Perché si vorrebbe fare questo è la vera domanda. Sembra che tutte le tue app saranno accoppiate in questo modo. E Google is eradicating singleton dalle sue app. Perché stai ritenendo opportuno riportarlo indietro?

+0

Potresti elaborare per creare un pool di oggetti separato? Sono abbastanza nuovo per J2EE quindi non so davvero cosa significhi, in particolare in termini pratici/di implementazione. Perché voglio un singleton? Sembrava una soluzione ragionevole alle mie esigenze: un luogo centralizzato per lo stato persistente nel corso della vita dell'app. Questi includono informazioni sulla configurazione dell'app e grandi quantità di dati caricati una volta da un database all'avvio dell'app (memorizzati successivamente durante il runtime). Non sono contrario ad usare qualcosa di diverso da un singleton, ma non conosco una soluzione migliore. –

+0

"Sembrava una soluzione ragionevole alle mie esigenze: un luogo centralizzato per lo stato persistente per tutta la vita dell'app." - Questo è quello che dovrebbero essere i database. Sai che è necessario averlo tutto in memoria? Le prestazioni sono un problema, o pensate che sarà un problema? Una soluzione migliore sarebbe usare una cache distribuita come Terracotta costruita per questo. Non è facile da fare - non dare per scontato che tu sappia meglio. – duffymo

1

È possibile utilizzare un MBean e collegarlo a JNDI, quindi recuperarlo ovunque si desideri utilizzarlo.

Il MBean potrebbe essere implementato in un file .sar

+0

Sfortunatamente, le ricerche JNDI sono lente (a quanto ho capito). È davvero un'opzione fattibile e scalabile? –

+1

Non dovrebbe essere troppo lento nella stessa JVM. Nel caso in cui le prestazioni fossero troppo elevate, è comunque possibile archiviare il risultato della ricerca da qualche parte – Guillaume

1

Se si utilizza Java EE 6, supporta gli EJB singleton.

+1

Sono abbastanza sicuro di non esserlo. Hai perso la parte di JBoss 5.1 vs 6? –

0

Se pratico, prendi semplicemente la classe che ha il singleton, mettilo in un JAR, prendi il JAR OUT del EAR e aggiungi il JAR al classloader JBoss (tramite il classpath di sistema o una directory lib). Questo mette la classe in un singolo programma di caricamento classe condiviso da entrambi i WAR.

Il singleton non sarà in grado di "vedere" alcunché nelle applicazioni WARs ecc., Poiché si trovano in un classloader inferiore.

Tuttavia, non vi è nulla che vi impedisca di iniettare nel singleton (all'avvio del server) una classe factory, originata da WARs et al, e la classe THAT ha accesso a tutte le classi app. Ciò rende il singleton più di un semplice contenitore.

Ma questo è semplice da fare.

Inoltre, se si esegue questa operazione, assicurarsi che quando si spegne l'applicazione, vengano liberate tutte le istanze in possesso di questo singleton. Qualsiasi riferimento alla classe da parte del singleton non sarà GC'd quando si annulla l'applicazione. Quindi, se hai un riferimento alla tua app memorizzata nel singleton, allora il server contiene un riferimento al classloader di singleton, quel classloader contiene un riferimento alla tua classe singleton, che detiene il riferimento alla tua classe app, che contiene un riferimento al app CLASSLOADER e THAT contiene un riferimento a tutte le classi nella tua app. Non un bel casino da lasciare alle spalle.

+0

Penso che l'ultimo paragrafo fornisca una buona ragione per evitarlo. Un altro è che si perde la capacità di hot-deploy. – Yishai

+0

Si perde la capacità di distribuire a caldo il Singleton effettivo, ma non il resto dell'applicazione. Quindi, una volta che lo sviluppo primario si è verificato, sì, è una verità sull'implementazione, ma probabilmente non è un vero problema del mondo reale. La chiave sta nel garantire che il ciclo di vita dell'applicazione funzioni correttamente in termini di avvio e distruzione del Singleton e delle risorse in esso contenute. Non è un peso orribile. Infine, Singleton può essere poco più di un riferimento statico che punta alla logica attuale e sofisticata all'interno dell'applicazione stessa. –