L’alternativa a Java EE: come implementare lo stack JSF+Spring+Hibernate con Java SE e Tomcat – Parte I

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:

Se lavorate con Maven, è disponibile il pom.xml 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:

  1. 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.

  2. aspectj-autoproxy attiva l’AOP basata sui proxy.

  3. 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.

  4. 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.ContextLoaderListener
	

	
		Faces Servlet
		javax.faces.webapp.FacesServlet
		1
	
	
		Faces Servlet
		*.jsf
	

	
		hibernateFilter
		
			org.springframework.orm.jpa.support.OpenSessionInViewFilter
	
	
		hibernateFilter
		Faces 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 famigerata LazyInitializationException.

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… chi è senza bug scagli la prima pietra!) 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.

Riferimenti

Andrea Como

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+