Spring Custom Scope

Introduzione

Lo scope di un bean è una caratteristica che qualifica il contesto all’interno del quale si sviluppa il suo ciclo di vita. Ad esempio lo scope indica quando il bean viene creato e distrutto e quante istanze possono coesistere all’interno dello stesso contesto di esecuzione. Spring supporta navitamente cinque tipi di scope che possono essere utilizzati all’interno del proprio web-aware context (ApplicationContext):

Scope Description
singleton Esiste una sola istanza del bean all’interno del container.
prototype E’ generata una istanza del bean ad ogni richiesta.
request In un contesto HTTP è generato un bean per ogni request, anche per request provenienti dallo stesso client. Al termine della request il bean è distrutto.
session In un contesto HTTP è generato un bean per ogni sessione. Al termine o allo scadere della sessione il bean è distrutto.
global session Simile al caso precedente ma il bean vive all’interno della sessione HTTP globale.

Sebbene questi scope siano sufficienti per la maggior parte delle applicazioni, esistono casi in cui può avere senso definire scope personalizzati. In Spring Batch, ad esempio, è presente lo scope task che limita la vita del bean all’interno dell’esecuzione del singolo task.

Custom Scope

Per la definizione di uno scope personalizzato Spring mette a disposizione l’interfacci org.springframework.beans.factory.config.Scope che è utilizzata dal container per rappresentare uno scope custom e memorizzare le istanze dei bean che vi appartengono. Spring fornisce anche un esempio di implementazione di custom scope  nella classe SimpleThreadScope che limita il ciclo di vita dei bean all’interno di un thread. Si noti che gli scope riportati nella tabella precedente non hanno classi che li implementano, semplicemente perchè sono hard-coded all’interno del factory bean di Spring.

Una volta definita la classe che implementa il custom scope, affinché possa essere utilizzato nell’applicazione, lo scope deve essere registrato nel container. Se utilizziamo l’XML per la configurazione dei bean, sarà sufficiente procedere inserendo la seguente porzione di codice xml che inietta il nuovo scope nella CustomScopeConfigurer:

Esempio Pratico

Vediamo un caso reale in cui è necessario l’utilizzo di uno scope non incluso in quelli standard. Supponiamo di dover esporre un servizio web che ha un comportamento diverso a secondo del client che lo invoca. Nella mia vita professionale mi è capitata una situazione simile implementando un servizio crittografico di cifratura utilizzabile da diverse applicazioni esterne, ciascuna con proprie chiavi crittografiche. Le chiavi erano conservate in un keystore e recuperato per mezzo dell’alias che ovviamente era funzione del chiamante. Quindi nel container IoC, per la stessa classe di cifratura, esistevano diverse istanze, una per chiamante e ciascuna configurata con l’alias specifico.

Procediamo però con un esempio ben più semplice, ovvero un servizio web HelloWord che restituisce un messaggio di benvenuto personalizzato.

Per l’implementazione del codice utilizziamo quanto già prodotto negli articolo JAX-WS InterceptorJava Context. In particolare utilizziamo la classe HeaderInterceptor che intercetta tutte le chiamate SOAP per recuperare l’header comune a tutte request, le quali contengono la proprietà sender. Header che viene poi conservata in un oggetto ThreadLocal attraverso la classe RequestContextHolder.

Implementiamo quindi un servizio HelloWorldImpl che espone il metodo SOAP greetings() e che utilizza un oggetto HelloWorldService. Tale oggetto è gestito dal container IoC di Spring che, per semplicità, è “avviato” direttamente dal servizio recuperando l’XML di configurazione applicationContext.xml dalla cartella resources:

Nell’interfaccia HelloWorld è attivato l’interceptor introdotto sopra e descritto inJAX-WS Interceptor:

Ricordiamo che affinché l’interceptor sia attivo su JBoss è necessario introdurre le dipendenze a CFX nel MANIFEST.MF. Sfortunatamente Maven non ci è di aiuto in questi casi quindi dobbiamo generare un manifest custom e chiedere a Maven di includerlo nel war al posto di quello generato automaticamente. Il manifest è il seguente:

mentre nel pom.xml il plugin per la generazione del war deve essere così configurato:

Custom Scope

L’interfaccia org.springframework.beans.factory.config.Scope prevede diversi metodo tra i quali il get() che è utilizzato da Spring per recuperare un bean dal container IoC o per generarne uno nuovo nel caso in cui il bean non sia istanziato (nel caso di singleton ad esempio). Osserviamo ad esempio la classe SimpleThreadScope che utilizza un oggetto java.util.Map (anche questo conservato in un oggetto ThreadLocal) dove sono memorizzati tutti i bean istanziati per quello scope. Il metodo get() semplicemente recupera il bean dalla mappa o lo genera utilizzando l’oggetto factory di input, inserendolo poi nella mappa utilizzando il nome del bean come chiave:

Utilizziamo la logica appena vista per implementare un SenderScope che restituisce bean diversi per la stessa classe in funzione dell’applicazione che invoca il metodogreetings(). Per farlo avremmo potuto implementare una multimappa ovvero un oggetto che conservi una mappa di bean per ciascun sender (es. la Multimap presente in Google Guevara). Per semplicità ho invece preferito utilizzare come chiave di recupero dei bean dalla mappa la composizione [sender].[bean name]. Il metodo get() dello SenderScope si presenta quindi così:

dove la chiave di memorizzazione è generata dal metodo getKey() nel modo descritto sopra accedendo all’header conservato nel RequestContextHeader:

Sender Aware

L’interfaccia SenderAware è utilizzata dal SenderScope per iniettare il sender in un bean da esso gestito. Operazione che è eseguita nel get() al momento della creazione del bean.

Si noti che non è obbligatorio che un bean con scope sender implementi tale interfaccia, perchè comunque il sender è sempre recuperabile dal RequestContextHeader, ma volendo mantenere un buon grado di disaccoppiamento tra le componenti questa è certamente la soluzione migliore.

Sender Scoped Bean

La classe HelloWordServiceImpl che è utilizzata dal web service implementa l’interfaccia SenderAware ed utilizza l’informazione sul sender per restituire un differente messaggio di benvenuto. Una semplice implementazione di tale classe è la seguente:

Ovviamente il bean deve essere configurato nel file applicationContext.xml con scope sender.

Esecuzione

Una volta generato il war ed eseguito il deploy su JBoss utilizziamo SoapUI per l’esecuzione dei test il cui risultato è riportato nella tabella seguente.

 
   

Codice Sorgente

l codice sorgente completo di tutti gli esempi presentati è scaricabile qui jaxws-webapp-scope.