La specifica Java EE 5 ha fatto grandi passi avanti rispetto alla versione precedente: adesso anche per noi comuni mortali è possibile usare gli Enterprise Java Beans (EJB) senza perdersi nella giungla di interfacce da implementare. Per poter usare questi strumenti potenti abbiamo però bisogno di una infrastruttura altrettanto potente, ovvero un Application Server (come JBoss, Glassfish o WebSphere) che implementa la specifica Java Enterprise. A seconda dei progetti, capita a volte di percepire questi strumenti come dei veri e propri “carrozzoni”, come se si volesse per forza usare un bazooka per schiacciare una formica. Abbiamo davvero bisogno sempre e comunque di un bazooka? Se il nostro target è la formica forse il gioco non vale la candela. In questo post vedremo come prendere in prestito quel che veramente ci serve del mondo Enterprise e riportarlo in un contesto più agile grazie al buon vecchio Tomcat.
Ti piace vincere facile?
Gli Application Server (AS) sono delle vere e proprie scatole magiche che ci rendono la vita spesso più facile: sanno fare un sacco di cose e anche bene! Gestiscono per esempio le connessioni e le transazioni al (o ai) database in modo trasparente all’utente, espongono servizi di chiamate remote dirette a metodi o con scambio di messaggi, dispongono di un servlet container per gestire le chiamate HTTP e molto altro ancora.
Usando un AS si ha la percezione che la nostra applicazione diventi una appendice dell’AS stesso, una sorta di plug-in, in quanto il server fornisce una miriade di servizi che la nostra applicazione va appunto a sfruttare. Questi servizi però consumano risorse, e anche tante, sia che vengano utilizzati o meno.
Se riteniamo che le dimensioni del nostro progetto siano tali da non giustificare tale consumo di risorse il downsize a Tomcat (o a qualsiasi altro servlet container) può essere una buona scelta. Si può parlare di downsize perché di fatto Tomcat nasce per gestire le chiamate HTTP tramite la specifica delle servlet: un Application Server invece possiede queste e molte altre funzionalità come accennato precedentemente.
Che alternative ho?
Non vi piace vincere facilmente? Abbiamo scelto il downsizing? Procediamo con Tomcat? La accendiamo? Bene. A questo punto siamo scesi dalle stelle alle stalle: se infatti lavorando con un AS avevamo tutto pronto, in questo caso invece abbiamo una scatola vuota! Con cosa la riempiamo?
Anche se abbiamo semplificato gli strumenti l’architettura a 3 livelli rimane sempre valida (mi raccomando!). Separando opportunamente il codice (non mettendo per esempio le query nelle pagine!!) si ottiene un codice molto più strutturato e soprattutto modellabile in componenti riusabili.
Conviene quindi scegliere dei framework che ci possano semplificare la vita in ogni layer per non stare a reinventare la ruota:
- Livello di Presentazione: Mojarra JSF 2.0
- Livello di Business: Spring 3.0
- Livello di Persistenza: Hibernate 3.5
Se guardiamo attentamente, due di questi framework (Mojarra e Hibernate) sono implementazioni della specifica EE (rispettivamente di JSF e JPA): può esserci utile questa strategia nel caso avessimo bisogno di saltare verso il mondo Enterprise. Spring può sostituire egregiamente i servizi locali offerti dagli EJB (si pensi alle transazioni o alla sicurezza per esempio) e in caso di necessità può integrarsi con essi. Siamo sicuri quindi di iniziare un nuovo progetto con tecnologie attuali che non ci impediranno eventuali upgrade.
Da dove cominciare
Per cominciare abbiamo bisogno di:
- una IDE di sviluppo: Eclipse Helios (o quello che preferite);
- ambiente di runtime: Apache Tomcat 7;
- le librerie suddette:
- JSF 2: Mojarra (o se preferite MyFaces);
- Spring 3.0.5.RELEASE;
- Hibernate 3.5.4;
Se lavorate con Maven, è disponibile il con tutte le librerie necessarie.
Per semplicità creeremo un solo progetto di tipo Web o Maven (anche se sarebbe opportuno creare almeno un progetto per layer) dove inseriremo tutte le librerie scaricate.
Configurazione base
Cominciamo quindi con i file di configurazione base. Per chi avesse già lavorato con Spring sa che la caratteristica base del framework è quella di fornire un Inversion of Control (IoC) container: questo significa che non faremo mai una new dei nostri oggetti, ma sarà il framework a preoccuparsi di come inizializzare e iniettare le referenze tra essi (implementazione chiamata Dependancy Injection – DI). Perché mai dovremmo affidarci ad un meccanismo di questo tipo e perdere il “potere della creazione”?? Forse perché c’è chi lo sa fare meglio di noi? No, non è per questo: basta menzionare le parole “disaccoppiamento” e “semplicità nello unit testing” e i dubbi passano subito!
Nel contesto della DI, le informazioni sulle dipendenze tra oggetti possono essere date al container sia tramite file XML che tramite annotazioni (grazie al cielo!): quest’ultima soluzione ci permette di evitare tonnellate di XML. Per mantenere la centralizzazione delle configurazioni, alcune impostazioni conviene comunque racchiuderle in un unico file XML in modo che siano facilmente e intuitivamente reperibili.
I file di configurazione che andremo a creare e che inseriremo nella cartella delle risorse saranno 4:
- jdbc.properties: parametri base della connessione al database;
- dbContext.xml: configurazione per integrare Spring e Hibernate per la comunicazione col il database (MySQL in questo caso). Può essere riusato: se si ha intenzione di creare un progetto Maven archetipo di questa soluzione, questo file deve essere incluso;
- applicationContext.xml: file di configurazione principale di Spring;
- hibernate.cfg.xml: file di configurazione principale di Hibernate.
Connessione al database: datasource e transazioni. Come integrare Spring e Hibernate
Si tratta della configurazione principale che possiamo tranquillamente lasciare in un unico file che chiameremo dbContext.xml:
< ?xml version="1.0" encoding="UTF-8"?>
${db.connection.driver_class}
${db.connection.url}
${db.connection.username}
${db.connection.password}
classpath:hibernate.cfg.xml
org.hibernate.cfg.AnnotationConfiguration
org.hibernate.dialect.MySQLInnoDBDialect
org.hibernate.cache.EhCacheProvider
true
true
true
update
true
org.hibernate.annotations.CacheConcurrencyStrategy
true
Analizziamo i bean dichiarati nel dettaglio:
-
dataSource: dichiarazione del datasource della nostra applicazione. Non importa definirlo nel server.xml di Tomcat: lasciamo che sia Spring a gestirlo. I parametri di connessione sono esterni (definiti in jdbc.properties) a questo file in modo da poter essere riusabile: una volta configurato, il progetto infatti potrebbe essere usato come archetipo Maven.
-
jdbcTemplate: classe di utility di Spring per semplificare l’interazione con JDBC nel caso se ne avesse bisogno. Il container IoC provvede ad inniettarvi l’istanza del datasource.
-
sessionFactory: è un wrapper della
SessionFactory
di Hibernate. Viene configurata tramite l’istanza del datasource, il file di configurazione di Hibernate (hibernate.cfg.xml), la configurationClass (per avviare l’elaborazione delle annotazioni sulle entità) e una serie di parametri di configurazione di Hibernate. Com’è fatto il file di configurazione di Hibernate? Eccone un semplice esempio:< ?xml version='1.0' encoding='UTF-8'?> < !DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
dove la classe
User
è una entità annotata con@Entity
come JPA comanda. Si risparmiano così i terribili file di mapping di Hibernate (ne servirebbe uno per ogni entità!!!). -
hibernateTemplate: configurato con la sessionFactory, è la classe fondamentale per interagire con il database. La classe espone una serie di metodi che nascondono l’oggetto
Session
di Hibernate e rendono la programmazione molto simile all’EntityManager
della Java EE. Più avanti vedremo come verrà iniettata in ogni DAO. -
transactionManager: configurato con la sessionFactory, è la classe responsabile della gestione delle transazioni.
-
txAdvice: a differenza degli altri, il transaction Advice non è un bean ma uno speciale tag che si avvale della AOP di Spring per gestire le transazioni. In particolare, in questo caso stiamo definendo che per ogni metodo che cominci con:
create
save
modify
update
delete
init
merge
deve essere eseguito in transazione. Metodi di quale classe? Lo vedremo a breve.
I nomi dei metodi richiamano tutte azioni che implicano operazioni di scrittura sul database. Se si preferisce, è possibile usare l’annotazione@Transactional
di Spring da applicare direttamente ai metodi interessati. A mio avviso, se si seguono certe regole nella nomenclatura dei metodi si ottiene un codice più leggibile per cui possiamo centralizzare tranquillamente in questo file XML la dichiarazione dei metodi transazionali.
Configurazione del dominio
Le configurazioni specifiche della nostra applicazione sono contenute nel file applicationContext.xml.
< ?xml version="1.0" encoding="ISO-8859-1"?>
Sfruttando le annotazioni disponibili da Spring 2.5 in poi è possibile ridurre il chilometrico applicationContext.xml in poche righe:
-
component-scan avvia la scansione delle classi, a partire dal package fornito, alla ricerca di annotazioni come
@Repository
,@Service
,@Controller
e così via. La documentazione su cosa implica l’uso del tag è piuttosto dettagliata. -
aspectj-autoproxy attiva l’AOP basata sui proxy.
-
una volta attivata l’AOP, possiamo definire su quali interfacce attivare la transazione. Tramite una espressione regolare, indichiamo a Spring di considerare transazionali tutti i metodi che seguono le regole definite dal transaction Advice. In questo caso, l’advice verrà applicato a tutti i metodi delle interfacce nel package specificato.
-
al termine, viene importato il file dbContext.xml.
Configurare la parte Web
Per il livello di presentazione abbiamo deciso di usare JSF 2.0. Di seguito il contenuto del file web.xml per la configurazione del progetto web:
< ?xml version="1.0" encoding="UTF-8"?>
myApp
javax.faces.PROJECT_STAGE
Development
javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL
true
javax.faces.STATE_SAVING_METHOD
server
javax.faces.FACELETS_SKIP_COMMENTS
true
javax.faces.FACELETS_REFRESH_PERIOD
3 org.springframework.web.context.ContextLoaderListenerFaces Servlet javax.faces.webapp.FacesServlet 1Faces Servlet *.jsf hibernateFilter org.springframework.orm.jpa.support.OpenSessionInViewFilter hibernateFilterFaces Servlet
Cerchiamo di spiegare nel dettaglio:
-
vengono configurati i parametri di contesto del JSF 2: i nomi sono piuttosto eloquenti;
-
la classe
ContextLoaderListener
è un ServletContextListener che viene chiamato all’avvio del Tomcat e inizializza Spring. -
JSF 2 viene inizializzato tramite la dichiarazione della servlet
FacesServlet
e mappata per tutte le richiese che finiscono per*.jsf
. -
infine, il filtro
OpenSessionInViewFilter
è fondamentale e chiude il giro dell’integrazione tra Spring e Hibernate nel mondo web. Questo filtro implementa l’omonimo pattern Open Session In View documentato ampiamente. Con un semplice filtro che mantiene aperta la sessione Hibernate finché la vista non è stata renderizzata si evita la famigerataLazyInitializationException
.
Quando Spring si avvia, cerca il file applicationContext.xml nella stessa cartella del web.xml, ovvero /WEB-INF: basta quindi crearne uno che a sua volta richiami quelli nella cartella risorse come segue:
< ?xml version="1.0" encoding="UTF-8"?>
< !DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
Perché non mettere allora tutto direttamente nella cartella WEB-INF? Semplicemente perché così possiamo usare JUnit per fare i nostri test (e guai a non farli… !) senza dover duplicare i file di configurazione nel progetto! JUnit infatti non riesce a leggerli all’interno della cartella WEB-INF.
Alla fine, dovremmo avere la struttura delle cartelle come segue:
-
resources
- jdbc.properties
- dbContext.xml
- applicationContext.xml
- hibernate.cfg.xml
-
(web root)/WEB-INF
- web.xml
- applicationContext.xml
Una mano verso il mondo Enterprise: Spring e JPA
Abbiamo detto che la scelta di questi framework non ci allontana molto dal mondo Enterprise, permettendoci un upgrade in caso di necessità. Se si usa però il classico HibernateTemplate
per nascondere Hibernate con Spring, la parte di codice dei DAO andrebbe comunque rivista.
Fortunatamente Spring ha il supporto per il puro JPA e a prescindere dal fatto che l’implementazione in questione sia Hibernate, implementa tutte le interfacce e le annotazioni tipiche del mondo enterprise. Apportando qualche modifica alle configurazioni viste, potremo lavorare con una istanza dell’EntityManager
come se fossimo in un EJB.
Cominciamo dal dbContext.xml. Vengono sostituiti i bean precedenti sessionFactory, hibernateTemplate e transactionManager con il seguente codice:
-
entityManagerFactory: istanza di
LocalContainerEntityManagerFactoryBean
che viene configurata tramite il nostro datasource, un adapter per l’implementazione di JPA che useremo (ovvero Hibernate) e il percorso del persistence.xml che potremmo collocare nella cartella delle risorse insieme agli altri file. Se non viene specificato, il file viene cercato nella cartella /META-INF. -
transactionManager: viene istanziato un gestore delle transazioni specifico per il mondo JPA, configurato con l’entityManagerFactory appena creato.
Se non abbiamo esigenze di configurazioni particolari, il file hibernate.cfg.xml può essere tralasciato e tutte le proprietà di configurazione di Hibernate possono essere inserite nel persistence.xml come segue:
it.cosenonjaviste.myapp.entities.User
Se scegliamo quindi di avvicinarci al JPA (scelta saggia!) ci rimane un’ultima cosa da modificare. Ricordate il filtro OpenSessionInView registrato nel web.xml? In questo caso non abbiamo più la sessione di Hibernate da aprire ma l’entity manager! Come si risolve? Basta chiedere a Spring! Sostituiamo il filtro precedente con:
hibernateFilter
org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
ed il gioco è fatto!
Conclusioni
Grazie a Java 5 e alle annotazioni usare framework come Spring, Hibernate o JSF 2 è diventato molto più semplice. Con qualche file di configurazione possiamo quindi appoggiarci a Tomcat per applicazioni di piccole/medie dimensioni. In un post successivo vedremo come scrivere e annotare il codice basato su un progetto configurato come abbiamo mostrato.
Pingback: ()
Pingback: ()
Pingback: ()