Con la versione 7 dell’application server, molte modifiche sono state apportate al meccanismo di classloading di JBoss. Adesso è possibile infatti utilizzare un sistema completamente differente basato sull’uso del concetto di Java Module, che anticipa il nuovo standard cheverrà (forse) introdotto nel prossimo JDK8.
JBoss 7 e la programmazione per moduli
Con la versione 7, JBoss Application Server introduce (per certi versi anticipa quanto dovrebbe essere definito dalla prossima specifica del JDK, ma non ci sono notizie certe a tal riguardo), un nuovo modo di assemblare le applicazioni tramite l’utilizzo dei moduli (che dovrebbero sostituire col tempo l’organizzazione delle librerie in classpath). Il tutto ha avuto inizio un paio di anni fa, quando un “certo” Mark Reinhold (Sun e Oracle Chief Architect per la piattaforma Java e OpenJDK,) annunciò al JavaOne del 2010 “The classpath is dead!”, dichiarando che il nuovo Java sarebbe stato organizzato secondo una logica a moduli.
Una rapida retrospettiva
Quello che poi è successo è storia (ed è raccontato anche nella serie di articoli di Luca Vetti Tagliati [JEV-3]): dall’acquisizione da parte di Oracle, ai ritardi delle pubblicazioni delle varie versioni. In tutta questa rivoluzione, il piano di rilascio del JDK ha subito una serie di ritardi enormi, tanto che sempre il succitato Mark Reinhold nel settembre del 2010 scriveva nel suo blog un post dal titolo “There’s not a moment to lose” (“Non c’è un attimo da perdere”), con un’introduzione piuttosto eloquente:
“È divenuto chiaro da tempo che il recente programma di sviluppo JDK 7 è, a dir poco, irrealistico. Abbiamo creato quel piano oltre nove mesi fa, prima dell’acquisizione di Sun da parte di Oracle. Il processo di post-acquisizione e di integrazione, purtroppo, ha richiesto più tempo di quello immaginato da tutti noi, ma ora siamo pronti e in grado di concentrarci su questa versione con un team più grande (e ancora in crescita) che continuerà a lavorare in piena trasparenza a fianco di altri collaboratori”.
Il tutto ha portato al rilascio del piano B, ossia di un JDK 7 nel 2011 senza alcuni dei progetti più controversi fra cui la parte relativa alla definizione dei moduli (progetto Jigsaw [JIG]) inzialmente rinviata alla versione Java SE 8, anche se adesso si parla già di postporlo alla versione JDK 9.
La strategia di JBoss 7
Il team di JBoss invece, come spesso è accaduto in passato, grazie a un processo interno più semplice e meno legato da vincoli (che invece ha chi deve portare avanti una specifica, come nel caso di Oracle che produce il JDK) ha già da tempo rilasciato un meccanismo del tutto simile a quello dei moduli.
Col nuovo modello organizzativo si abbandona il famoso classloading gerarchico (molto potente ma anche complesso a volte da gestire, specialmente quando le cose si complicavano) per passare a un più semplice modello basato sull’uso di moduli componibili (JBoss Modules Project [JBM]). L’introduzione dell’architettura a moduli (oltre alla prossima introduzione nel JDK, molto in voga in questo periodo anche grazie a progetti esterni come OSGi) di fatto estende il modello in uso per l’impacchettamento di applicazioni Java EE (vedi i file .ear o .war o .jar); un modulo può essere quindi una libreria, una collezione di classi o più genericamente una raccolta di risorse associate a un unico classloader: quindi, a differenza del passato, dove era il classloader che raccoglieva sotto una organizzazione gerarchica un set di classi, qui il punto di vista è esattamente rovesciato.
Ogni modulo contiene una serie di meta-informazioni che servono per descriverlo completamente, fra cui:
- il nome del modulo;
- un set di metadati estendibili con cui definire la versione, le resources, gli exports dal modulo (vedi oltre) e altro ancora;
- la lista dei moduli importati;
- la lista delle classi contenute nel modulo.
Per fare un esempio, si potrebbe immaginare di creare un modulo con nome com.mokabyte.config e in versione 1.0; in questo caso i metadati sarebbero così definiti:
module ( name = com.mokabyte.util extensible-metadata = [@Version("1.0")] )
Moduli ed esportazione/importazione di risorse
Classi e risorse disponibili all’interno di un modulo (sia perche’ presenti, sia perche’ importate da altri moduli) possono essere esportate in modo che siano disponibili per l’importazione da parte di altri moduli; per esempio, supponiamo che nel modulo com.mokabyte.util avessimo definito le tre classi ClassA, ClassB, ClassC, delle quali solo la terza si volesse rendere pubblica all’esterno del modulo per il suo utilizzo. Questo risultato si potrebbe ottenere tramite la seguente definizione:
module ( name = com.mokabyte..util extensible-metadata = [@Version("1.0")] class-exports = [com.mokabyte.util.ClassC] members = [com.mokabyte.util.ClassA, com.mokabyte.util.ClassB, com.mokabyte.util.ClassC] )
In questo modo stiamo dicendo cosa compone il modulo (members) e cosa dichiariamo come visibile dall’esterno. Stiamo anche definendo la versione del modulo (@Version). Rispetto al passato queste due informazioni (scope e version) permettono di migliorare considerevolmente il modo in cui il classpath viene composto tramite la semplice aggregazione di JAR. Infatti, se da un lato possiamo definire cosa un modulo esporta all’esterno, dall’altra parte è possibile specificare cosa possiamo importare in un altro modulo: esattamente quali classi e quale versione del bundle. Seguendo l’esempio di prima, ipotizzando di avere una applicazione contenuta nel modulo com.mokabyte.myapp che voglia usare la classe ClassC di prima, si dovrebbe scrivere:
module ( name = com.mokabyte.app extensible-metadata = [@Version("1.0")] imports = [ImportModule(com.mokabyte.util, @VersionConstraint("1.0")) members = [com.mokabyte.app.ClassA, com.mokabyte.app.ClassB, com.mokabyte.app.ClassC] )
In questo caso stiamo dicendo che vogliamo importare il modulo com.mokabyte.util (o meglio le classi qui definite come pubbliche) ma limitatamente alla versione 1.0 (questo filtro riduce enormemente il numero di conflitti a cui eravamo abituati con le versioni precedenti della Java EE).
Se si pensa alla quantità di problemi dovuti a class-clash o class-notfound, questa nuova organizzazione dovrebbe finalmente semplificare il lavoro dei programmatori.
Maggiori approfondimenti sulla natura e funzionamento dei moduli in Java EE si possono trovare presso [MOD].
Dopo questa doverosa introduzione, verrebbe da chiedersi come la gestione dei moduli si implementi nell’ambito di una applicazione Java EE deployata in JBoss7. Il tema è affrontabile da diversi punti di vista e sommariamente possiamo dire che si hanno i seguenti casi:
- Implicit Module Dependency: utilizzo del set dei moduli offerti dall’application server
- organizzazione del classpath di una applicazione EE e sua composizione col classpath di sistema
- possibilità per il programmatore di definire e deployare un suo modulo all’interno dell’application server.
Vedremo qui di seguito il primo e il secondo punto, tralasciando per brevità la possibilità di creare moduli propri da aggiungere all’elenco dei moduli di sistema. Per chi fosse interessato si può consultare il seguente riferimento [JMM].
Moduli offerti da JBoss7: implicit module dependency
Il meccanismo dei moduli impliciti di JBoss permette di rendere automaticamente disponibili per le applicazioni quelle risorse che sono necessarie per il loro funzionamento. Per esempio se un .ear contiene uno o più EJB, avrà bisogno dei package della Java EE, i quali sono contenuti in appositi moduli già presenti all’interno dell’application server. Tale dipendenza potrebbe essere definita per mezzo del meccanismo degli import tramite opportuni metadati come si è appena potuto vedere nel paragrafo precedente
Dato che sarebbe scomodo per il programmatore configurare uno per uno i package più comunemente usati, essi sono resi disponibili all’applicazione e per non violare la regola dell’importazione dei moduli vista poc’anzi si dice in questo caso che viene offerta una dipendenza implicita da parte dell’application server.
Il meccanismo della dipendenza implicita è resa possibile tramite il processamento dei vari .ear, .war, .jar deployati da parte di una catena di deployment processors: ognuno di questi strumenti verifica se il contenuto del bundle risponde a determinati requisiti e in tal modo attiva o meno la dipendenza automatica (di fatto aggiunge il modulo al classpath della applicazione).
Il controllo può essere fatto verificando la presenza di annotazioni particolari: per esempio se viene trovata la annotazione @Stateless allora l’EJB deployment processor entrerà in azione aggiungendo la dipendenza per il modulo Java EE API.
Oltre a questo meccanismo va ad aggiungere module-API specifici in aggiunta a quelli disponibili di default; tale lista è consultabile presso [IMPL].
Fine tuning sull’implicit module dependency
Nel caso si voglia raffinare il meccanismo delle dipendenze implicite si può utilizzare il file jboss-deployment-structure.xml che dovrà essere inserito all’interno della applicazione che si vuole configurare (all’interno della META-INF per applicazioni EE o WEB-INF per le web app).
Questo file permette di:
- limitare l’aggiunta di dipendenze automatiche;
- aggiungere ulteriori dipendenze rispetto a quelle disponibili;
- definire moduli aggiuntivi (in particolare definire i nomi con cui verranno pubblicati);
- modificare il tipo di isolamento sulla visibilità dei bundle contenuti all’interno di un .ear (si veda il paragrafo relativo poco più avanti).
Di seguito è riportato un esempio preso dalla documentazione ufficiale (in rete si trovano ottimi build Maven per creare il file voluto):
true
Class Loading all’interno di applicazioni EE
Un problema piuttosto comune che si incontra quando si sviluppano applicazioni Java EE è quello della sovrapposizione di classi fra quelle caricate dal container e quelle caricate dalla applicazione (cosa che porta a sovrapposizioni, caricamenti di versioni differenti e in definitiva al fallimento del deploy della applicazione stessa). In JBoss 7, sebbene sia stato abbandonato il modello gerarchico, continua a valere la regola della precedenza sul caricamento delle classi; quindi è stato definito un ordine ben preciso sull’ordine delle dipendenze fra moduli, secondo lo schema qui riportato (in ordine da quello a priorità maggiore al minore):
- System Dependencies: queste sono aggiunte al modulo automaticamente (dipendenze implicite). Da notare che il set di moduli di sistema (vale a dire preconfigurati al momento della installazione dell’application server), può essere arricchito tramite l’aggiunta di moduli definiti dall’utente (vedi il prossimo punto)-
- User Dependencies: dipendenze che sono aggiunte tramite il file jboss-deployment-structure.xml (vedi il paragrafo precedente) o tramite l’elemento Dependencies: nel manifest (si veda il paragrafo relativo più avanti), o equivalentemente per mezzo del classico Class-Path: per aggiungere altri JAR files.
- Local Resource: aggiunge al classpath le classi impacchettate all’interno del bundle stesso per esempio all’interno della cartella WEB-INF/classes o della WEB-INF/lib del WAR file.
- Inter Deployment Dependencies: dipendenze che sono legate ad altre dipendenze definite in altri pacchetti o nello stesso; per esempio si possono considerare la lib del file .ear, oppure classi definite in altri EJB JAR.
Nello specifico poi, ogni applicazione segue regole che sono derivate più o meno direttamente dalle specifiche ufficiali. Di seguito sono presentati i vari casi.
WAR Class Loading
Un file .war è considerato un solo singolo modulo per cui le classi contenute all’interno di WEB-INF/lib e quelle di WEB-INF/classes sono considerate allo stesso modo; le due location sono entrambe caricate dallo stesso classloader.
EAR Class Loading
Un bundle .ear è, come noto, un file contenitore la cui struttura segue quanto definito dalla specifica Java EE: al suo interno si possono inserire quindi più moduli e questo ha degli impatti piuttosto importanti sul caricamento delle classi: la specifica infatti dice che (nostra traduzione):
“…le applicazioni portabili non dovrebbero fare affidamento su subdeployments che hanno accesso ad altri subdeployment a meno che nel file MANIFEST.MF non sia sta impostata una esplicita voce Class-Path. Quindi, le applicazioni portabili dovrebbero sempre usare una entry Class-Path per stabilire esplicitamente le proprie dipendenze, vale a dire Class-Path: /X:/libs/libFile.jar. È inoltre possibile effettuare l’override del valore dell’elemento ear-subdeployments-isolated a un livello scelto per ciascun deploy. Maggiori informazioni sono presenti nelle specifiche nella sezione su jboss-deployment-structure.xml below“.
Da questo punto di vista JBoss ridefinisce (o meglio aggiunge) alcune semplici regole legate al meccanismo di caricamento delle classi: di base possiamo dire che non tutte le classi all’interno del .ear hanno accesso alle classi contenute all’interno degli altri moduli, a meno che non siano state esplicitate delle dipendenze specifiche. Di base la directory EAR/lib viene vista come un singolo modulo indipendente dagli altri (gli eventuali .war o .jar). Ogni modulo web (WAR) o ejb (JAR) è isolato dagli altri (non è definita alcuna dipendenza incrociata fra .war e .jar) ma ha una diretta dipendenza al modulo EAR che di riflesso fornisce visibilità (dipendenza) al modulo EAR/lib. Questo meccanismo è illustrato in figura 1.
Figura 1 – Meccanismo di dipendenze all’interno del bundle EAR.
Questa organizzazione è regolata da quanto specificato nella sezione ear-subdeployments-isolated (presente nel file jboss-deployment-structure.xml):
false
Per default tale parametro è impostato a false, che di fatto permette ai vari subdeployments di vedere le reciproche classi. Per esempio se avessimo una struttura come la seguente
MokabyteAppEE.ear
|
|— MokabyteAppWeb.war
|
|— MokabyteAppEJB1.jar
|
|— MokabyteAppEJB2.jar
con ear-subdeployments-isolated=false, allora le classi della MokabyteAppWeb potranno vedere quelle di MokabyteAppEJB1 e MokabyteAppEJB2, così come le due applicazioni EJB potranno accedere alle rispettive classi. Il modulo web invece, come espressamente definito all’interno della specifica Java EE, resta sempre sotto il controllo di un classloader isolato: le applicazioni EJB non potranno accedere alle classi.
Nel caso in cui il valore del flag sia impostato a true, allora l’application server non attiva alcun meccanismo di dipendenza automatica; si rientra quindi nel caso standard come da specifica, in cui le dipendenze sono regolate tramite l’utilizzo del parametro Class-Path all’interno del Manifest.mf (o comunque definendo in JBoss le explicit module dependencies).
Nota: importazione di moduli e nomi di moduli
Un importante aspetto da tener presente, e che sarà chiaro piu avanti, è che ogni modulo riceve un nome che segue le seguenti regole:
- per i moduli di primo livello (per intendersi nel caso in cui il .war o .jar sia deployato direttamente senza inclusione in un .ear) il nome in genere è una cosa del tipo deployment.myarchive.war
- per i sotto-moduli (quando si procede a un impacchettamento completo tramite un file .ear) il nome assume la seguente forma deployment.myear.ear.mywar.war.
Questo permette di specificare il modulo che si vuole importare, utilizzando il nome univoco del modulo stesso (vedremo poco più avanti come fare).
Utilizzo delle dipendenze tramite Manifest Entries
Oltre alla possibilità di utilizzare le dipendenze implicite offerte dall’application server, si può continuare a utilizzare il sistema offerto dalla specifica Java EE (quindi indispensabile se si vuole mantenere una maggiore portabilità) basato sull’utilizzo della entry Dependencies: all’interno del manifest entry. In questo caso si tratta di una lista separata da “,” dei nomi dei moduli che si vogliono aggiungere all’applicazione (in realtà, dato che tale sistema è stato introdotto prima della nascita del concetto di modulo, sarebbe più corretto parlare di composizione del classpath tramite l’inclusione di librerie). Nel caso si vogliano usare i moduli disponibili nell‘application server, si potrà consultare la cartella JBOSS_HOME/modules; un esempio di composizione di tale entry potrebbe essere la seguente:
Dependencies: org.javassist export,org.apache.velocity export services,org.antlr
Ogni dipendenza può inoltre specificare alcuni parametri fra cui:
- export: significa che la dipendenza sul modulo specificato (p.e.: org.javassist) potrà essere esportata verso ogni altro modulo che dipende da quello in questione (ossia quello dove è definita questa Dependencies).
- services: Java 6 introduce un concetto nuovo legato alla Dependency Injection (DI), vale a dire la possibilità di definire interfacce per comportamenti e relative implementazioni; in una parola serivzi (vedi [SER] e [SL]). Per default gli elementi contenuti in META-INF di una dependency non sono accessibili; tramite questo tag, gli elementi in META-INF/services sono accessibili e quindi i relativi servizi possono essere caricati.
- optional: specifica se il modulo è opzionale, cioè se deve bloccare il deploy nel caso in cui non venga trovato.
Riferimenti
[JMOD] Modularized Java with JBoss Modules –http://planet.jboss.org/view/post.seam?post=modularized_java_with_jboss_modules
[JEV-3] L’evoluzione di Java: verso Java 8
https://www.mokabyte.it/cms/article.run?permalink=mb173_javaevolution-3
[JIG] Project Jigsaw
http://openjdk.java.net/projects/jigsaw/
[JMM] JBoss Modules Example – Modular Web Application
http://www.javacodegeeks.com/2011/09/jboss-modules-example-modular-web.html
[MOD] Introduction to Java Module System in Java 7.0
http://www.javabeat.net/2009/01/introduction-to-java-module-system-in-java-7-0/
[IMPL] Implicit module dependencies for deployments
https://docs.jboss.org/author/display/AS7/Implicit+module+dependencies+for+deployments
[SER] Simple Dependency Injection with ServiceLoader in JDK 6
http://weblogs.java.net/blog/timboudreau/archive/2008/08/simple_dependen.html
[SL] Class ServiceLoader’s Javadoc
http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html