Spring

II parte: il coredi

Questa seconda parte tratta il modulo Core di Spring. Il modulo Core rappresenta in pratica un‘implementazione del pattern Inversion of Control, che è un tentativo di superare alcuni limiti insiti nella programmazione Object Oriented più ortodossa. Le funzionalità del Core qui esposte vengono largamente utilizzate negli altri moduli di Spring, e costituiscono per così dire un framework nel framework. Nelle successive quattro "puntate" di questa panoramica su Spring verranno esposti gli argomenti relativi alle altre funzionalità offerte dal framework, nell‘ordine: l‘implementazione dell‘AOP di Spring, l‘Accesso ai dati, l‘MVC orientato alle generiche applicazioni web, l‘MVC orientato alle portlets.


Introduzione

La storia dell‘informatica, come del resto qualunque percorso di conoscenza, va avanti non seguendo un percorso lineare di chissà  quale armonioso disegno, ma attraverso una successione infinita di correzioni di rotta e nuovi studi. La programmazione orientata agli oggetti (Object Oriented) è apparsa nel mondo dello sviluppo software come una piccola rivoluzione, eppure già  ci si arrovella per superarne taluni limiti. Cosଠcome nella fisica non si è ancora riusciti a elaborare un modello teorico atto a comporre gli aspetti asimmetrici delle forze della natura, cosଠil microcosmo della programmazione non ha ancora trovato il suo paradigma definitivo. Il pattern Inversion of Control (IoC) e la programmazione Aspect Oriented (AOP) rappresentano due tentativi di dare risoluzione a un aspetto che tiene da sempre sulle spine progettisti e sviluppatori: minimizzare le dipendenze tra i componenti applicativi. In quest‘articolo viene passata in rassegna l‘implementazione di Spring del pattern IoC, mentre ci si occuperà  dell‘AOP nell‘articolo successivo.

Minimizzare le dipendenze

Prendiamo una ipotetica classe ArticoloServiceImpl. La immaginiamo una semplice classe che implementa un qualche aspetto gestionale di un‘entità  "Articolo" rappresentato da una classe omonima. La classe Articolo è un semplice Java Bean con alcuni semplici attributi, e in questo momento non ci interessa sapere nient‘altro. La classe ArticoloServiceImpl ha due semplici metodi. InsertArticolo permette di inserire un‘istanza di Articolo in un archivio, mentre il metodo loadArticolo permette di recuperare un articolo dall‘archivio in base al titolo (per semplicità  consideriamo il titolo come chiave identificativa dell‘entità  Articolo). Nella più classica modellazione l‘accesso ai dati è gestito da oggetti DAO, in questo caso la classe ArticoloDAOImpl. La classe ArticoloDAOImpl in poche parole "collabora" con la classe ArticoloServiceImpl perchà© essa possa espletare la sua logica applicativa. Ma in che modo la classe ArticoloServiceImpl accede ai servigi della ArticoloDAO? Uno scenario molto semplice potrebbe essere questo:

public class ArticoloServiceImpl {

public Articolo getArticolo(String titolo) {
        ArticoloDAOImpl articoloDAO = new ArticoloDAOImpl();
        return articoloDAO.loadArticolo(titolo);
    }
    public void insertArticolo(Articolo articolo) {
        ArticoloDAOImpl articoloDAO = new ArticoloDAOImpl();
        articoloDAO.insertArticolo(articolo);
    }

}

Qui l‘implementazione di ogni metodo utilizza l‘operatore "new" per ottenere una‘istanza della classe ArticoloDAOImpl. Cosa c‘è che non va in quest‘approccio? Immaginiamo di fare quella cosa che per pigrizia non facciamo quasi mai: il testing unitario. Vogliamo scrivere un caso di test per la classe ArticoloServiceImpl e ci accorgiamo subito di una cosa: non possiamo testare la classe in modo unitario, in perfetto isolamento dagli altri componenti (in questo caso la classe DAO), senza modificare il codice! Questo perchà© nella filosofia dei cosiddetti "mock tests" dobbiamo fornire un implementazione del DAO in qualche modo fasulla, che si comporti come si aspetta la classe che l‘utilizza, ma niente di più (nessun accesso a risorse dati reali). In questo caso saremmo costretti a sostituire la classe ArticoloDAOImpl con qualcosa del tipo ArticoloDAOMock, fare il test e poi ripristinare il codice precedente. Facciamo quindi un passo avanti seguendo il manuale del buon programmatore ad oggetti e "nascondendo" l‘implementazione specifica della classe DAO dietro una generica interfaccia del tipo:

public interface ArticoloDAO {

public Articolo loadArticolo(String titolo);
    public void insertArticolo(Articolo articolo);

}

Il codice precedente diventerebbe:

public class ArticoloServiceImpl {
    public Articolo getArticolo(String titolo) {
        ArticoloDAO articoloDAO = new ArticoloDAOImpl();
        return articoloDAO.loadArticolo(titolo);
    }
    public void insertArticolo(Articolo articolo) {
        ArticoloDAO articoloDAO = new ArticoloDAOImpl();
        articoloDAO.insertArticolo(articolo);
    }
}

Nonostante la buona volontà  non abbiamo fatto molti progressi. Siamo costretti comunque a fornire un‘istanza di un‘implementazione specifica dell‘interfaccia ArticoloDAO. Una soluzione potrebbe essere quella di utilizzare una classe che implementi il pattern "Factory" che potrebbe essere una classe singleton oppure statica. Qualcosa del genere:

public class ArticoloServiceImpl {
    public Articolo getArticolo(String titolo) {
	ArticoloDAO articoloDAO =  DAOFactory.getInstance().getDAO(ArticoloDAO.class);
        return articoloDAO.loadArticolo(titolo);
    }
    public void insertArticolo(Articolo articolo) {
	ArticoloDAO articoloDAO = DAOFactory.getInstance().getDAO(ArticoloDAO.class);
        articoloDAO.insertArticolo(articolo);
    }
}

Qui la classe singleton DAOFactory si occupa di fornire l‘implementazione specifica dell‘interfaccia ArticoloDAO in base al tipo dell‘interfaccia stesso. Il codice non dipende più dall‘implementazione specifica di ArticoloDAO, questa dipendenza è stata "centralizzata" nella classe Factory. La situazione è sicuramente migliore perchà© per effettuare i nostri test dobbiamo solamente modificare la classe "Factory", ma non è ancora quello che ci aspettiamo. Ma prima di capire cosa propone il pattern IoC vediamo un altro esempio. Immaginiamo che la nostra ArticoloServiceImpl sia in realtà  un Session Bean stateless nell‘ambito di in un‘architettura Enterprise Java Beans. Il codice client per utilizzare il session bean deve fare qualcosa del tipo:

Context initial = new InitialContext();
Context myEnv = (Context) initial.lookup("java:comp/env");
Object ref = myEnv.lookup("ejb/ArticoloServiceHome");
ArticoloServiceHome home = (ArticoloServiceHome)
PortableRemoteObject.narrow(ref, ArticoloService.class);
articoloService = home.create();

Tutto questo codice solo per recuperare un singolo oggetto! Potrebbe essere più semplice utilizzare una classe "ServiceLocator":

ArticoloServiceHome home = ServiceLocator.locate(ArticoloServiceHome);
ArticoloService articoloService = home.create();

Cosଠil codice è sicuramente più sintetico, anche se allo stesso modo della Factory precedente ci costringe a fare un "lookup" dell‘oggetto voluto. Inoltre in entrambe i casi il codice della classe che implementa il servizio viene a dipendere da oggetti specifici come la factory nel primo caso e il ServiceLocator nel secondo.

Vediamo cosa propone l‘IoC:

public class ArticoloServiceImpl {

private ArticoloDAO articoloDAO;
    public Articolo getArticolo(String titolo) {
	return articoloDAO.loadArticolo(titolo);
    }
    public void insertArticolo(Articolo articolo) {
	articoloDAO.insertArticolo(articolo);
    }

//Metodo setter utilizzato dall‘Inversion of Control (o Dependency //Injection)
    public void setArticoloDAO(ArticoloDAO articoloDAO) {
        this.articoloDAO = articoloDAO;
    }
}

L‘IoC suggerisce che sia una qualche infrastruttura esterna in base a delle informazioni di configurazione a occuparsi di gestire il ciclo di vita della classe e di "iniettare" la dipendenza rappresentata da un‘implementazione dell‘interfaccia ArticoloDAO attraverso il metodo setArticoloDAO. Per inciso, il termine iniettare ricorda una definizione alternativa per il pattern IoC: Dipendency Injection, sicuramente più esplicativa, anche se nella letteratura sull‘argomento è più ricorrente la definizione Inversion of Control. Tornando comunque ai fatti concreti, l‘esempio precedente potrebbe essere riscritto anche in questo modo:

public class ArticoloServiceImpl {

private ArticoloDAO articoloDAO;
	//Costruttore utilizzato dall‘Inversion of Control (o Dipendency //Injection)
    	public ArticoloServiceImpl(ArticoloDAO articoloDAO) {
        this.articoloDAO = articoloDAO;
	}

public void insertArticolo(Articolo articolo) {
	articoloDAO.insertArticolo(articolo);
	}

public Articolo getArticolo(String titolo) {
		return articoloDAO.loadArticolo(titolo);
	}

}

In questo caso viene utilizzato un costruttore invece di un metodo set. Da questi esempi possiamo concludere che nella filosofia dell‘IoC la classe ArticoloServiceImpl non ha nessuna necessità  di preoccuparsi di recuperare l‘oggetto di cui ha bisogno, esso gli viene fornito automaticamente da un qualche framework.

Un progetto di esempio su Eclipse

Per utilizzare Spring nei semplici esempi che verranno presentati è sufficiente scaricare l‘ultima versione dal sito www.spring.org (spring-framework-2.0-with-dependencies), creare un progetto Java in Eclipse che possiamo chiamare EsempiSpring, e importare nel classpath di tale progetto la libreria spring.jar presente nella cartella dist e la commons-logging.jar presente nella cartella lib della distribuzione di Spring appena scaricata.

La BeanFactory

Nel precedente paragrafo abbiamo visto come il concetto di IoC prevede che le dipendenze degli oggetti debbano essere fornite agli stessi da una qualche infrastruttura esterna tramite dei metodi set oppure dei costruttori opportuni. Spring implementa un "container" che attraverso informazioni di configurazione scritte in un qualche supporto come per esempio un file xml permette di gestire il ciclo di vita delle proprie classi e l‘inizializzazione delle loro proprietà  e dipendenze. Il concetto di container è implementato in pratica da una implementazione dell‘interfaccia BeanFactory, che fornisce il supporto basilare all‘IoC. Riprendiamo in dettaglio le classi di esempio usate precendemente e inseriamole nel progetto Java precedentemente creato. Creiamo un package spring.esempi.core e all‘interno di questo le seguenti classi:

package spring.esempi.core;

import java.util.Set;

public class Articolo {
    String titolo;
    Set autori;
    String contenuto;
    public Set getAutori() {
        return autori;
    }
    public void setAutori(Set autori) {
        this.autori = autori;
    }
    public String getContenuto() {
        return contenuto;
    }
    public void setContenuto(String contenuto) {
        this.contenuto = contenuto;
    }
    public String getTitolo() {
        return titolo;
    }
    public void setTitolo(String titolo) {
        this.titolo = titolo;
    }
} 

package spring.esempi.core;

public interface ArticoloService {
    public Articolo getArticolo(String titolo);
    public void insertArticolo(Articolo articolo);
}

package spring.esempi.core;

public interface ArticoloDAO {

public Articolo loadArticolo(String titolo);
    public void insertArticolo(Articolo articolo);

}
package spring.esempi.core;

public class ArticoloServiceImpl implements ArticoloService{
    private ArticoloDAO articoloDAO;

public Articolo getArticolo(String titolo) {
        return articoloDAO.loadArticolo(titolo);
    }
    public void insertArticolo(Articolo articolo) {
        articoloDAO.insertArticolo(articolo);
    }

public ArticoloDAO getArticoloDAO() {
        return articoloDAO;
    }
    //Metodo setter utilizzato da Spring per
    //l‘"iniezione" dell‘oggetto DAO "collaborante"
    public void setArticoloDAO(ArticoloDAO articoloDAO) {
        this.articoloDAO = articoloDAO;
    }

}

package spring.esempi.core;

import java.util.HashMap;
import java.util.Map;

public class ArticoloDAOImpl implements ArticoloDAO {
    private static Map articoli = new HashMap();
    public void insertArticolo(Articolo articolo) {
        articoli.put(articolo.getTitolo(), articolo);    
    }

public Articolo loadArticolo(String titolo) {
        return (Articolo)articoli.get(titolo);
    }

}

Queste classi servono a illustrare le funzionalità  dell‘IoC di Spring e non è necessario che siano ritagliate perfettamente su un caso reale. Cosଠnella classe Articolo l‘attributo "titolo" è usato come dato identificativo dell‘entità  stessa e il campo "contenuto" è una semplice stringa. Inoltre l‘implementazione di ArticoloDAO non fa riferimento a nessun database: viene utilizzata una variabile statica di tipo Map per simulare un semplice archivio (che non tiene ovviamente conto delle problematiche di accesso concorrente ai dati!). Spring ci permette di gestire il ciclo di vita di questi oggetti attraverso uno o più file di configurazione. Per il nostro esempio creiamo un file context.xml e memorizziamolo nella cartella principale della cartella del progetto. Il suo contenuto sarà  questo:




        
        
        
            
                Autore1
                Autore2
            
        
    
    
        
    
    

Abbiamo in pratica definito i tre beans che compongono il nostro esempio. L‘attributo id del tag "bean" serve a fornire un‘identificativo univoco all‘interno del container mentre l‘attributo class fornisce il nome esteso della classe. Possiamo inizializzare i beans con dei valori o settare le dipendenze da altri oggetti attraverso il tag "property". L‘attributo name di property deve essere uguale al nome del campo definito nella classe. L‘attributo value serve a inizializzare il campo con un valore fornito in rappresentazione stringa, mentre l‘attributo ref serve a impostare il campo con il riferimento a un altro bean definito all‘interno del container. I campi "titolo" e "contenuto" del bean "articolo" vengono qui inizializzati attraverso l‘attributo value ai valori "TITOLO" e "CONTENUTO". Perchà© il container sia in grado di effettuare l‘inizializzazione dell‘istanza di Articolo, questo stesso deve avere i metodi "setter" relativi ai campi suddetti. E‘ possibile anche utilizzare un approccio che preveda dei costruttori al posto dei metodi "setter" e in questo caso il tag property dovrebbe essere sostituito dal tag "constructor-arg" (vedere la guida di riferimento di Spring). Nel nostro esempio i campi "titolo" e "contenuto" della classe Articolo sono effettivamente degli oggetti di tipo String e il contenuto del campo value può essere convertito in un tipo String in modo immediato e senza ambiguità . Ma se invece fossero altri tipi complessi e volessimo inizializzarli utilizzando lo stesso meccanismo descritto sopra? Spring riprende dalla tecnologia Java Beans l‘interfaccia PropertyEditor e ne fornisce le implementazioni specifiche per una certa casistica di tipi. L‘interfaccia PropertyEditor fornisce l‘astrazione per convertire stringhe di caratteri aventi certe caratteristiche negli oggetti corrispondenti. Per esempio potremmo voler rappresentare un campo numero di telefono con un oggetto Telefono avente i campi "prefisso" e "numero" e inizializzarlo attraverso la configurazione di Spring descritta sopra, tramite l‘attributo value. L‘implementazione di PropertyEditor specifica di questo caso dovrebbe essere in grado di convertire un valore stringa del tipo "040/888999" in un‘istanza di Telefono (nel seguito dell‘articolo sarà  trattato il meccanismo utilizzato da Spring per "registrare" nel container dei property editors personalizzati). Tornando alla nostra configurazione la definizione del bean "articolo" ci offre una particolarità : la configurazione e inizializzazione di un campo di tipo Set, il campo "autori". Spring fornisce degli elementi di configurazione specifici per impostare dei dati di tipo List, Set, Map ecc. . La configurazione dei due restanti beans "articoloService" e "articoloDAO" ci mostra invece come è possibile "iniettare" negli oggetti le dipendenze da altri oggetti. La definizione di "articoloService" contiene una "property" con un attributo "ref" che contiene l‘identificativo del bean "articoloDAO" definito più sotto. Il container si aspetta che la classe ArticoloServiceImpl implementi un metodo "setter" per un campo di tipo ArticoloDAO, e richiama tale metodo fornendogli un istanza di articoloDAO. Possiamo ora scrivere un metodo "main" per testare il funzionamento del container (la BeanFactory):

package spring.esempi.core;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

public class Test {

/**
     * @param args
     */
    public static void main(String[] args) {
        Resource resource = new ClassPathResource("context.xml");
        BeanFactory beanFactory = new XmlBeanFactory(resource);
        Articolo articolo = (Articolo) beanFactory.getBean("articolo");
        System.out.println(articolo.getTitolo());
        System.out.println(articolo.getContenuto());
        System.out.println(articolo.getAutori().toString());
	ArticoloService articoloService 
	= (ArticoloServiceImpl) beanFactory.getBean("articoloService");
        articoloService.insertArticolo(articolo);
        Articolo articoloArchiviato 
        = articoloService.getArticolo(articolo.getTitolo());
        System.out.println(articoloArchiviato.getContenuto());
        System.out.println(articoloArchiviato.getAutori().toString());
    }

}

Viene prima istanziata una implementazione specifica di BeanFactory, la classe XmlBeanFactory, che si aspetta una configurazione sotto forma di file xml. In questo caso il file di configurazione viene letto dal classpath attraverso l‘utilizzo di una classe ClassPathResource. Poi viene invocato il metodo getBean(...) con l‘identificativo del bean "articolo". Possiamo vedere che quello che ci viene restituito è un‘istanza di Articolo correttamente inizializzata con i valori definiti in context.xml. Successivamente viene recuperata l‘istanza di ArticoloServiceImpl ed eseguito il metodo insertArticolo con in ingresso il riferimento al bean "articolo". Il bean "articoloService" utilizza l‘istanza di "articoloDAO" che gli viene fornita dal container per effettuare la memorizzazione di "articolo" nell‘archivio. Infine viene verificata la corretta memorizzazione di "articolo" invocando il metodo getArticolo(...) con l‘identificativo "TITOLO" e stampando il valore dei campi "contenuto" e "autori". Occorre mettere in evidenza che il metodo getBean(...) normalmente restituisce le istanze dei beans in modalità  "singleton", ossia restituisce sempre la stessa istanza. Questo significa che è il container stesso a gestire il ciclo di vita dei beans e non il codice che ne ottiene i riferimenti attraverso getBean("..."). Se per qualche motivo si vuole evitare questo comportamento occorre settare l‘attributo "singleton" al valore "false".

In questo caso il container tratta il bean "articolo" in modalità  cosiddetta "prototype", restituisce cioè una nuova istanza ogni volta che si invoca il metodo getBean(...).

Da questo esempio si potrebbe obbiettare che il codice contenuto nel metodo main dipende da Spring, perchà© deve utilizzare la BeanFactory per recuperare i riferimenti ai beans. E‘ inevitabile che debba esistere un qualche "frammento" del nostro sistema "consapevole" dell‘esistenza di Spring, tuttavia normalmente esso rappresenta una sezione ben circoscritta, che può essere agevolmente "spostata" su un qualche componente infrastrutturale. Per esempio nel caso di un‘applicazione web che utilizzi il pattern MVC, potremmo limitare l‘utilizzo del container di Spring nel "Front Controller", che si occuperebbe di gestire il "dispathcing" delle "actions" fornite dal container stesso secondo i meccanismi dell‘esempio descritto in questo paragrafo e di conseguenza indipendenti da Spring.

L‘ApplicationContext

L‘ApplicationContext è un container più avanzato rispetto alla BeanFactory. Estende la BeanFactory e fornisce funzionalità  ulteriori:

  • Fornisce strumenti per risolvere messaggi di testo e per l‘internazionalizzazione dei messaggi stessi (I18N)
  • Fornisce degli strumenti standard per caricare delle risorse, come per esempio delle immagini.
  • Permette di pubblicare degli eventi ai beans definiti nell‘ApplicationContext stesso e che implementino l‘interfaccia ApplicationListener

Ci sono diverse implementazioni dell‘ApplicationContext che si possono utilizzare tra le quali:

  • ClassPathXmlApplicationContext: permette di caricare l‘ApplicationContext da un file xml presente nel classpath dell‘applicazione
  • FileSystemXmlApplicationContext: permette di caricare l‘ApplicationContext da un file xml presente nel file system (occorre specificare un percorso assoluto)

Poichà© l‘ApplicationContext è solo un estensione della BeanFactory, ne riprende tutte le funzionalità  cosଠche possiamo riscrivere l‘esempio precedente in questo modo:

public class Test {
 
/**
     * @param args
     */
    public static void main(String[] args) {
        ApplicationContext context 
        = new ClassPathXmlApplicationContext("context.xml");
        Articolo articolo = (Articolo) context.getBean("articolo");
        System.out.println(articolo.getTitolo());
        System.out.println(articolo.getContenuto());
        System.out.println(articolo.getAutori().toString());
        ArticoloService articoloService 
        = (ArticoloServiceImpl) context.getBean("articoloService");
        articoloService.insertArticolo(articolo);
        Articolo articoloArchiviato 
        = articoloService.getArticolo(articolo.getTitolo());
        System.out.println(articoloArchiviato.getContenuto());
        System.out.println(articoloArchiviato.getAutori().toString());
 
}
}

Nella maggior parte delle applicazioni conviene utilizzare l‘ApplicationContext, mentre l‘utilizzo di una semplice BeanFactory è consigliata solo nei casi in cui l‘utilizzo della memoria sia un fattore critico. E‘ essenziale specificare che c‘è una differenza nel modo in cui vengono caricati i beans di tipo singleton nell‘ApplicationContext rispetto alla BeanFactory: la BeanFactory istanzia i singoli bean solo al momento della chiamata del metodo getBean(...), mentre l‘ApplicationContext li istanzia tutti al momento dello startup. Questo assicura tra l‘altro che eventuali errori siano segnalati in fase di avvio dell‘ApplicationContext.

Il ciclo di vita dei beans e i PostProcessors

Finora abbiamo visto che i beans che costituiscono il nostro sistema software sono configurati da un container, che può essere la più semplice BeanFactory oppure l‘ApplicationContext. Il container gestisce il ciclo di vita dei beans. Comprendere il ciclo di vita è importante perchà© Spring permette di inserirsi nelle varie fasi con delle interfacce opportune e dei meccanismi di callback. Vediamolo un po‘ in dettaglio:

  • Il container attraverso la configurazione istanzia il bean.
  • Attraverso le informazioni di configurazione secondo le regole dell‘ Ioc setta le proprietà  del bean.
  • Se il bean implementa l‘interfaccia BeanNameAware viene richiamato il metodo setBeanName(...) passandogli in ingresso l‘identificativo del bean stesso.
  • Se ci sono dei BeanPostProcessors registrati all‘interno del container viene eseguito il loro metodo postProcessBeforeInitialization().
  • Se è specificato un metodo personalizzato attraverso l‘attributo init-method del tag bean (), questo viene chiamato dal container.
  • Infine se ci sono dei BeanPostProcessors registrati all‘interno del container viene eseguito il loro metodo postProcessAfterInitialization().

Nel momento il cui il container decide di sbarazzarsi del bean poi:

  • Se è definito un metodo personalizzato definito dall‘attributo destroy-method per il bean, questo viene chiamato dal container.

Esiste un‘alternativa ai metodi personalizzati definiti dagli attributi init-method e destroy-method: è possibile fare implementare al bean le interfacce InitializingBean e DisposableBean che prevedono rispettivamente i metodi afterPropertiesSet() e destroy(). Quest‘approccio però è decisamente sconsigliato perchà© rende i bean dipendenti da Spring. Anche l‘utilizzo dell‘interfaccia BeanNameAware, che permette al bean di accedere al proprio identificativo, soffre dello stesso problema, e si consiglia di evitarne l‘utilizzo a meno che non sia assolutamente necessario.

Tra le possibilità  di inserirsi nel ciclo di vita dei beans quella di definire all‘interno del container dei BeanPostProcessors è particolarmente potente. L‘interfaccia BeanPostProcessor è mostrata di seguito:

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String name)
            throws BeansException;
    Object postProcessAfterInitialization(Object bean, String name) 
            throws BeansException;
}

Basta scrivere una classe che implementi questa interfaccia, e se si utilizza la BeanFactory occorre registrarla col metodo:

BeanPostProcessor myPostProcessor = new MyPostProcessor();
factory.addBeanPostProcessor(myPostProcessor);

Se si utilizza l‘ApplicationContext invece è sufficiente definire la classe MyPostProcessor come un qualunque bean:


    class="spring.esempi.core.MyPostProcessor"/>

A questo punto prima della fase di inizializzazione di ogni bean (definita da un eventuale init-method) verrà  richiamato il metodo postProcessBeforeInitialization(...)e dopo l‘inizializzazione il metodo postProcessAfterInitialization(...).

Esiste anche il concetto di BeanFactoryPostProcessor che attraverso il metodo postProcessBeanFactory(...) permette di aggiungere del comportamento dopo che il container ha caricato le definizioni dei beans ma prima che gli stessi siano istanziati. Non va quindi a inserirsi nel ciclo di vita dei beans ma in quello dello stesso container:

public interface BeanFactoryPostProcessor {
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 
            throws BeansException;
}

Per registrare un BeanFactoryPostProcessor nel caso di utilizzo di un ApplicationContext è sufficiente definirne in configurazione il bean relativo (che implementa l‘interfaccia sopra mostrata), mentre non è possibile utilizzare dei BeanFactoryPostProcessor con containers di tipo BeanFactory.

Registrare un Property Editor personalizzato

Per restare in tema di post-processors, vediamo in questo paragrafo come si possa gestire la configurazione di property editors personalizzati all‘interno del container utilizzando un BeanFactoryPostProcessor. Abbiamo già  accennato ai property editors predefiniti di Spring. Per fornire un proprio property editor occorre implementare un‘estensione della classe PropertyEditorSupport che rappresenta un‘implementazione dell‘interfaccia PropertyEditor. L‘interfaccia PropertyEditor ha diversi metodi tra i quali:

  • setAsText(String value): setta il valore della proprietà  di un bean a partire dalla stringa passata in ingresso
  • getAsText():ritorna la rappresentazione di tipo String del valore di una proprietà .

Immaginiamo di avere un bean MyBean del quale ci interessa sapere solo che ha un campo di tipo Nominativo, che è a sua volta un semplice bean con attributi nome e cognome:

public class Nominativo {
    String nome;
    String cognome;
    public String getCognome() {
        return cognome;
    }
    public void setCognome(String cognome) {
        this.cognome = cognome;
    }
    public String getNome() {
        return nome;
    }
    public void setNome(String nome) {
        this.nome = nome;
    }
}

Vogliamo gestire il "settaggio" della proprietà  Nominativo del bean MyBean a partire dalla definizione:


    class="spring.esempi.core.MyBean">
    
    Bill Clinton
    

Qui la proprietà  nominativo viene definita da un valore stringa che contiene nome e cognome separati da uno spazio. Abbiamo bisogno di un property editor che sia in grado di elaborare questo valore e convertirlo in un oggetto Nominativo che possa essere settato sul campo nominativo di MyBean, secondo la definizione sopra mostrata. Scriviamo a tal scopo una classe NominativoEditor come sotto riportato:

public class NominativoEditor extends PropertyEditorSupport {

    public void setAsText(String nominativo) throws IllegalArgumentException {
        String[] temp = nominativo.split(" ");
        String nome = temp[0];
        String cognome = temp[1];
        Nominativo nom = new Nominativo();
        nom.setNome(nome);
        nom.setCognome(cognome);
        this.setValue(nom);
    }
 
}

Come si vede questa classe estende la PropertyEditorSupport e implementa il metodo setAsText(...). L‘implementazione non fa altro che elaborare la stringa in ingresso, creare di conseguenza un oggetto Nominativo inizializzato con nome e cognome e fornirlo in ingresso al metodo setValue(...) . A questo punto dobbiamo solo utilizzare un BeanFactoryPostProcessor predefinito di Spring, il CustomEditorConfigurer, che non fa altro che caricare gli editor personalizzati nella BeanFactory chiamando il metodo registerCustomEditor(...). E‘ sufficiente fornire la definizione del CustomEditorConfigurer nel file di configurazione di Spring:

        
        
        
            
                class=" spring.esempi.core.NominativoEditor">
            
        
        
    

A questo punto qualunque attributo di tipo Nominativo che venga inizializzato nella configurazione di Spring tramite il sotto-tag "value" di un tag "property" utilizzerà  NominativoEditor per effettuare la conversione nella classe Nominativo ed impostare di conseguenza il campo come descritto nell‘esempio.

Catturare gli eventi

L‘ApplicationContext pubblica degli eventi particolari che sono sottoclassi della org.springframework.context.ApplicationEvent. L‘evento ContextRefreshedEvent per esempio è pubblicato quando l‘ApplicationContext è inizializzato o aggiornato. Se vogliamo che un bean risponda a uno di questi eventi, dobbiamo far implementare al bean stesso l‘interfaccia org.springframework.context.ApplicationListener. Per registrare il bean come "listener" degli eventi pubblicati dall‘ApplicationContext non dobbiamo far altro che definire il bean stesso all‘interno del file di configurazione. Una volta caricato il bean dall‘ApplicationContext, il metodo onApplicationEvent(ApplicationEvent event) implementato dal bean stesso sarà  richiamato per ogni specifico evento. Se vogliamo pubblicare invece i nostri eventi personalizzati dobbiamo prima di tutto scrivere una classe che estenda ApplicationEvent, per esempio:

public class MyEvent extends ApplicationEvent {
 
public MyEvent(Object source) {
        super(source);
    }
 
}

e in una sezione del codice del nostro sistema che ha a disposizione l‘ApplicationContext, eseguire il metodo publishEvent(ApplicationEvent event) dello stesso come di seguito mostrato:

ApplicationContext context = ...;
context.publishEvent(new MyEvent(this));

Rendere i beans "consapevoli" di Spring

Utilizzare Spring e al tempo stesso rendere i nostri beans indipendenti da esso vuol dire seguirne la filosofia di base, ma se per caso fosse necessario fornire ai beans stessi un qualche accesso al framework, ciò è possibile. Le interfacce ApplicationContextAware e BeanFactoryAware hanno rispettivamente i metodi setApplicationContext(...) e setBeanFactory(...). Un bean che implementa una di queste interfacce riceve automaticamente un riferimento all‘ApplicationContext o alla BeanFactory. Per esempio nel caso dell‘ApplicationContext:

...
public void setApplicationContext(ApplicationContext context) {
    this.context = context;
}
...

Ã? anche possibile implementare l‘interfaccia BeanNameAware con il metodo setBeanName(...) che permette al bean di accedere al proprio nome (l‘identificativo utilizzato nella configurazione):

...
public void setBeanName(String beanName) {
    this.beanName = beanName;
}
...

Questo può essere utile nel caso della presenza di più istanze dello stesso bean nell‘ApplicationContext, in modo da poter effettuare dei tracciamenti in base all‘identificativo e al tipo della classe.

Conclusioni

Abbiamo visto gli aspetti essenziali del modulo Core di Spring, con l‘implementazione del pattern IoC e le altre funzionalità  di contorno che concorrono a una infrastruttura "leggera" e flessibile. Gli altri moduli di Spring utilizzano tutti queste funzionalità  di base e costituiscono nell‘insieme un framework evoluto per applicazioni "enterprise". La filosofia insita nella progettazione di Spring si mostra in tutta chiarezza: fornire allo sviluppatore la possibilità  di arricchire i propri beans di funzionalità  evolute in modo del tutto trasparente, senza persino che i beans stessi "sappiano" dell‘esistenza di Spring. E‘ comunque possibile utilizzare una qualche dipendenza più esplicita, mantenendo in questo modo una estrema flessibilità . Nel prossimo articolo si tratterà  dell‘implementazione dell‘AOP di Spring, altro concetto essenziale nella strada tortuosa del "disaccoppiamento" degli strati applicativi.

Riferimenti

[1] "Spring Reference" 2.0, http://www.springframework.org/

[2] Craig Walls - Ryan Breidenbach, "Spring in Action", Manning, 2005

Condividi

Pubblicato nel numero
112 novembre 2006
Mario Casari si è laureato in Fisica a Cagliari. Ha lavorato come progettista e sviluppatore soprattutto in ambito J2EE per diverse e importanti realtà italiane. Attualmente sta approfondendo le tematiche di utilizzo di Spring in progetti per la pubblica amministrazione.
Articoli nella stessa serie
  • Spring
    I parte: introduzione al framework
  • Spring
    III parte: Il supporto di Spring per l‘Aspect Oriented Programming
  • Spring
    IV parte: accesso ai dati
  • Spring
    V parte: Web MVC
  • Spring
    VI parte: Portlet MVC
Ti potrebbe interessare anche