Ci sono casi in cui occorre rendere modulare un progetto già esistente, che integra diverse tecnologie ‘ereditate’, come possono esse JSP, Spring , Spring MVC, e così via. In questo articolo vedremo come partire da un progetto basato su Maven che genera un unico WAR, e come possiamo trasformare le dipendenze di librerie di terze parti in moduli da caricare mediante modules, togliendole così dal WAR risultante.
Da dove iniziare
Prima di tutto occorre scaricarsi da internet gli strumenti che servono per iniziare a utilizzare jboss-modules.
Application Server
AS7: al momento in cui scrivo l’articolo, l’ultima versione stabile è la 7.1.1, ed è ovviamente il punto di partenza.
Esempi di prova
Quickstarts è un sito web [1] contenente esempi di prova di tutte le tecnologie JBoss, tra cui ci sono anche esempi su Java EE 6, integrazione con Spring e modules. Non è strettamente necessario dare un’occhiata a questo insieme di esempi, ma è un buon punto di inizio per avere delle linee guida su come utilizzare tutte le singole tecnologie messe a disposizione dal server di JBoss.
IDE
JBoss Developer Studio: è l’IDE di riferimento [2] realizzato da JBoss per utilizzare le loro tecnologie. Di fatto è una distribuzione di Eclipse fatta apposta da JBoss. Il Developer Studio si basa sulla distribuzione Java Enterprise di Eclipse, quindi utilizzando questo IDE non si perde nulla rispetto a una qualsiasi altra distro di Eclipse per il mondo Java. Inoltre è ovviamente possibile estendere il Developer Studio integrando altri plugin (p.e. SpringIDE, GWT, etc.). Durante l’installazione del Developer Studio, è bene fare attenzione a configurare correttamente dove si trova una distribuzione di JBoss AS7 sul proprio filesystem
Figura 1 – Download di JBoss AS7.1.1 e Quickstarts.
Creazione di un progetto EE basato su Spring
Iniziamo ora un progetto di un sito Web basato solamente sulla tecnologia Spring. In particolare il progetto dovrà integrare le seguenti tecnologie:
- Maven: è il tool di compilazione;
- Le librerie Spring: è il framework in sè e l’insieme di librerie da usare;
- Hibernate, JPA e JTA per la parte business;
- JSP con la libreria Spring MVC per la parte vista.
Tramite JBoss Developer Studio è possibile partire già da un progetto che integra assieme tutte queste tecnologie senza doverlo fare da soli a mano. Facendo partire il Developer Studio, comparirà automaticamente la vista del “JBoss Central”, che contiene:
- le news dal mondo JBoss;
- link di wizard per la creazione di progetti Java EE;
- link al JBoss Quickstarts;
- update e installazione di pacchetti.
Figura 2 – JBoss Central.
Creiamo adesso un progetto basato su SpringMVC, cliccando sul wizard “Spring MVC Project”. A questo punto l’IDE verificherà che abbiamo installato tutti i plugin necessari.
Figura 3 – Creazione del progetto Spring MVC.
Al passo successivo scegliamo il nome del progetto (in questo esempio il nome sarà quello di default: springmvc).
Figura 4 – Creazione del progetto Spring MVC.
A questo punto si completa il wizard e il progetto è pronto.
Adesso possiamo accendere JBoss AS7 da un’altra shell (possiamo anche farlo partire da dentro l’IDE, è sostanzialmente la stessa cosa). Per compilare e installare il progetto su JBoss AS7 è sufficiente lanciare il comando:
mvc clean package jboss-as:deploy
In questa maniera compiliamo e distribuiamo il WAR risultante su JBoss e il risultato è il classico esempio in cui gestiamo una semplice rubrica di indirizzi email messi in una tabella con la possiblità di aggiungere/togliere gli elementi selezionati:
Figura 5 – Welcome to JBoss!
L’esempio riportato quindi produce un classico WAR con dentro tutte le dipendenze espresse nel progetto. Quindi il progetto risultante sarà un WAR monolitico. Vediamo ora di renderlo modulare tramite jboss-modules.
Modularizzazione del progetto basato su Spring MVC
Per modularizzare un progetto di questo tipo dobbiamo andare per gradi. Prima di tutto occorre individuare quelle librerie esterne al nostro software, staccarle dalle dipendenze Maven, e creare al loro posto dei moduli jboss-modules e installarli dentro la nostra istanza di AS7.
Quello che faremo ora è quindi staccare tutte le librerie Spring, creare un modulo unico che contiene tutte le librerie Spring della distribuzione 3.2.0 e renderlo disponibile a tutte le applicazioni installate dentro la nostra istanza di AS7.
Creazione del pacchetto del modulo Spring
La scelta è quella di fare un unico modulo che contenga anche la parte MVC, e seguiamo questa via per semplicità: il modulo risultante verrebbe fuori molto grosso e “monolitico” a sua volta, ma questo è solo un esempio di come si deve procedere per portare dei vecchi progetti su jboss-modules.
Prima di tutto scarichiamoci dal sito di Spring l’ultima distribuzione (allo stato attuale è la 3.2.0). Scegliamo di dare il nome org.springframework al nuovo modulo main sotto la cartella dei moduli del server AS7.
cd ~/Applications/AS7/modules mkdir -P org/springframework/main
A questo punto copiamo dentro questa cartella tutti i JAR che abbiamo preso dalla distribuzione Spring. La cartella dovrebbe quindi ora apparire così:
~/Applications/AS7/modules/org/springframework/main$ ls
aopalliance-1.0.jar spring-aspects-3.2.0.RELEASE.jar
spring-core-3.2.0.RELEASE.jar spring-jms-3.2.0.RELEASE.jar
spring-web-3.2.0.RELEASE.jar cglib-nodep-2.2.
spring-beans-3.2.0.RELEASE.jar spring-expression-3.2.0.RELEASE.jar
spring-orm-3.2.0.RELEASE.jar spring-webmvc-3.2.0.RELEASE.jar
commons-logging-1.1.1.jar spring-context-3.2.0.RELEASE.jar
spring-instrument-3.2.0.RELEASE.jar spring-oxm-3.2.0.RELEASE.jar
spring-webmvc-portlet-3.2.0.RELEASE.jar spring-aop-3.2.0.RELEASE.jar
spring-context-support-3.2.0.RELEASE.jar spring-jdbc-3.2.0.RELEASE.jar
spring-tx-3.2.0.RELEASE.jar
Creiamo il descrittore di modulo module.xml, aggiungiamo le risorse che abbiamo scaricato dal sito di Spring e aggiungiamo le dipendenze dai moduli JBoss che gli servono per poter funzionare correttamente in un ambiente Java EE, per esempio i moduli per il parsing XML (dom4j), per le Servlet (javax.servlet.api con assieme JSP e JSTL), aggiungiamo una dipendenza da Hibernate, dal modulo per le transazioni, dai log (log4j e log di JBoss), da Java Assist per l’instrumentazione delle classi.
Il file diventa come segue:
Il modulo Spring è ora pronto: effettuiamo un undeploy del WAR che abbiamo appena fatto, spegniamo e riaccendiamo il server AS7. A questo punto vediamo che compariranno dei file .index all’interno della cartella del nuovo modulo: questo succede perchè modules indicizza i pacchetti che trova all’interno di ogni JAR, in modo da velocizzare la ricerca di dove si trova una classe da caricare.
Il modulo è ora pronto per essere utilizzato, dobbiamo solamente legarlo al progetto che abbiamo appena fatto.
Integrazione con il nuovo modulo Spring
Prima di tutto analizziamo bene il file di progetto Maven che abbiamo utilizzato: tutte le dipendenze dal framework Spring che troviamo nel POM non vanno rimosse, ma va aggiunto lo scope provided in modo tale da non avere errori a tempo di compilazione e da non aggiungere le librerie di spring nel WAR.
xsi_schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 org.moka.tools.example.springmvc springmvc war 0.0.1-SNAPSHOT Getting Started with Spring on JBoss 3.2.0.RELEASE 1.0 1.1.2 1.1.1 7.1.1.Final … org.springframework spring-aop provided org.springframework spring-asm provided org.springframework spring-beans provided org.springframework spring-context provided … org.springframework spring-tx provided org.springframework spring-web provided org.springframework spring-webmvc provided … …
Adesso che abbiamo tolto Spring dal deploy nel WAR dobbiamo aggiungere a questo WAR una dipendenza dal modulo Spring. Secondo le specifiche Java EE 6, il file descrittore specifico del vendor dell’Application Server che stiamo usando deve trovarsi dentro la directory WEB-INF del WAR; e il nome è ovviamente differente per ogni vendor. Per JBoss il nome è: jboss-deployment-structure.xml. Ecco come lo troviamo dall’esempio del Quickstarts:
Aggiungiamo ora la dipendenza dal modulo Spring in questo modo:
Come si può vedere, abbiamo aggiunto la dipendenza da org.springframework, ma abbiamo anche detto che importiamo tutto ciò che si trova sotto le directory META-INF e org; facciamo questo per poter importare tutti i file XML, i descrittori, etc. di cui Spring ha bisogno. Inoltre, in particolare, Spring MVC ha anche bisogno delle sue librerie di tag JSP, quindi dei file .tld che si trovano nel file spring-webmvc-3.2.0.RELEASE.jar.
Ricompiliamo il progetto ed effettuiamo il deploy su AS7. Il deploy sembra andare bene:
16:23:34,164 INFO [org.jboss.web] (MSC service thread 1-6) JBAS018210: Registering web context: /springmvc
Un problema particolare
Se però andiamo con il browser a richiamare la home page del nostro sito troveremo il messaggio mostrato in figura 6
Figura 6 – Errore riscontrato nel progetto modularizzato.
E sul log di AS7, potremo leggere quanto segue:
16:32:25,628 ERROR [org.apache.catalina.core.ContainerBase.[jboss.web] .[default-host].[/springmvc].[jsp]] (http--127.0.0.1-8080-5) Servlet.service() for servlet jsp threw exception: org.apache.jasper.JasperException: The absolute uri: http://www.springframework.org/tags/form cannot be resolved in either web.xml or the jar files deployed with this application at org.apache.jasper.compiler.DefaultErrorHandler.jspError(DefaultErrorHandler.java:51) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.ErrorDispatcher.dispatch(ErrorDispatcher.java:409) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.ErrorDispatcher.jspError(ErrorDispatcher.java:116) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.TagLibraryInfoImpl.generateTLDLocation(TagLibraryInfoImpl.java:239) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.TagLibraryInfoImpl.(TagLibraryInfoImpl.java:152) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.Parser.parseTaglibDirective(Parser.java:386) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.Parser.parseDirective(Parser.java:448) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.Parser.parseElements(Parser.java:1398) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.Parser.parse(Parser.java:130) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.ParserController.doParse(ParserController.java:255) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.ParserController.parse(ParserController.java:103) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.Compiler.generateJava(Compiler.java:194) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.Compiler.compile(Compiler.java:360) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.Compiler.compile(Compiler.java:340) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.Compiler.compile(Compiler.java:327) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:607) [jbossweb-7.0.13.Final.jar:] … 16:32:25,646 ERROR [org.apache.catalina.core.ContainerBase.[jboss.web] .[default-host].[/springmvc].[jboss-as-kitchensink]] (http--127.0.0.1-8080-5) Servlet.service() for servlet jboss-as-kitchensink threw exception: org.apache.jasper.JasperException: The absolute uri: http://www.springframework.org/tags/form cannot be resolved in either web.xml or the jar files deployed with this application at org.apache.jasper.compiler.DefaultErrorHandler.jspError(DefaultErrorHandler.java:51) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.ErrorDispatcher.dispatch(ErrorDispatcher.java:409) [jbossweb-7.0.13.Final.jar:] at org.apache.jasper.compiler.ErrorDispatcher.jspError(ErrorDispatcher.java:116) [jbossweb-7.0.13.Final.jar:] …
Come mai riscontriamo questo errore? Cerchiamo prima di tutto di capire che tipo di errore è. Da quello che leggiamo sembra che Catalina non riesca a trovare il file dove sono definite le librerie di tag di Spring (spring-form.tld e, a cascata, tutti gli altri, situati dentro la cartella META-INF di spring-webmvc.jar). Come mai dal WAR non riusciamo ad accedere al file TLD? Eppure nelle dipendenze di modulo abbiamo specificato di prendere tutti i file situati dentro le cartelle META-INF della famiglia di JAR di Spring.
Effettivamente jboss-modules riesce a servire questi files al nostro WAR, ma il file viene comunque ignorato perchè lo standard delle JSP asserisce che le librerie di tag devono obbligatoriamente stare dentro il WAR, e in particolare devono stare:
- dentro un qualsiasi JAR all’interno della directory WEB-INF/lib del WAR;
- dentro la directory WEB-INF.
Quindi è per questa ragione che non riusciamo a localizzare i nostri file TLD.
Deve essere chiaro che questo problema non dipende in alcun modo da jboss-modules: infatti avremmo ottenuto lo stesso risultato utilizzando OSGi. Si tratta di un caso specifico in cui non è possibile in alcun modo modularizzare una parte del progetto che stiamo facendo, a causa delle restrizioni dello standard. L’abbiamo presentato proprio per far capire che anche la modularizzazione ha i suoi problemi, che possono comunque essere superati.
La soluzione
La soluzione a questo problema risiede nell’iniettare a mano i file TLD dentro il WAR a tempo di compilazione. Per effettuare questo “hackeraggio” occorre modificare il file maven utilizzando il plugin maven-dependency-plugin nel seguente modo:
org.apache.maven.plugins maven-dependency-plugin extract-tld generate-resources unpack org.springframework spring-webmvc ${version.spring} src/main/webapp/WEB-INF META-INF/*.tld
Questo meccanismo può essere utilizzato in ogni occasione in cui sia necessario copiare dei file all’interno del WAR a causa di una non compatibilità di librerie/standard con la modularità appena introdotta.
Conclusioni
Abbiamo portato su JBoss AS7 una applicazione che usa Spring, modularizzandone le dipendenze. Portare comunque una vecchia applicazione non modulare in un ambiente modulare può incontrare dei problemi come quello risolto in questo articolo; il processo di risoluzione potrebbe non essere breve.
L’approccio più sensanto, quindi, è il seguente: per progetti web che partono da zero è sempre meglio utilizzare da subito tecnologie già pensate per essere modulari , come CDI/EJB, invece di tentare di integrare le nuove tecnologie con librerie più vecchie anche se ci sembrano “più comode”; alla lunga, questa scelta apparentemente comoda potrebbe dare più problemi che benefici.
Riferimenti
[1] Quickstarts, un sito contenente numerosi esempi
http://www.jboss.org/jdf/quickstarts/get-started/
[2] L’IDE JBoss Developer Studio
https://devstudio.jboss.com/download/6.x.html