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
:
1 2 3 4 5 6 7 8 |
<bean id="customScope" class="..."/> <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="workflow" value-ref="customScope"/> </map> </property> <bean/> |
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 Interceptor e Java 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@WebService(endpointInterface = "it.javaboss.jaxws.HelloWorld", targetNamespace = "http://hello.world.ns/", name = "HelloWorld", serviceName = "HelloWorldService", portName = "HelloWorldPort") public class HelloWorldImpl implements HelloWorld { private ApplicationContext context; private HelloWordService getHelloWordService() { if ( context == null ) { context = new ClassPathXmlApplicationContext("applicationContext.xml"); } return (HelloWordService) context.getBean("helloService"); } public String greetings(Request request) { return getHelloWordService().getGreetings( request.getPerson() ); } } |
Nell’interfaccia HelloWorld
è attivato l’interceptor introdotto sopra e descritto inJAX-WS Interceptor:
1 2 3 4 5 6 |
@WebService(name = "HelloWorld", targetNamespace = "http://hello.world.ns/") @SOAPBinding(style = Style.DOCUMENT, use = Use.LITERAL, parameterStyle = ParameterStyle.WRAPPED) @InInterceptors(interceptors = { "it.javaboss.jaxws.HeaderInterceptor" }) public interface HelloWorld { String greetings(Request person); } |
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:
1 2 3 |
Manifest-Version: 1.0 Dependencies: org.apache.cxf, org.apache.ws.security, org.apache.cxf.impl Class-Path: |
pom.xml
il plugin per la generazione del war deve essere così configurato:
1 2 3 4 5 6 7 8 9 10 |
<plugin> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> <archive> <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile> </archive> </configuration> </plugin> |
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:
1 2 3 4 5 6 7 8 9 |
public Object get(String name, ObjectFactory<?> objectFactory) { Map<String, Object> scope = this.threadScope.get(); Object object = scope.get(name); if (object == null) { object = objectFactory.getObject(); scope.put(name, object); } return object; } |
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ì:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public Object get(String name, ObjectFactory<?> objectFactory) { String key = getKey(name); if( !scopedObjects.containsKey( key ) ) { Object obj = objectFactory.getObject(); // // Se il bean implementa SenderAware gli viene iniettato il sender // if ( SenderAware.class.isAssignableFrom( obj.getClass() ) ) { String sender = RequestContextHolder.getCurrentRequestHeader().getSender(); ((SenderAware)obj).setSender(sender); } scopedObjects.put( key, obj ); } return scopedObjects.get( key ); } |
dove la chiave di memorizzazione è generata dal metodo getKey()
nel modo descritto sopra accedendo all’header conservato nel RequestContextHeader:
1 2 3 4 5 6 7 8 |
private String getKey( String name ) { String sender = RequestContextHolder.getCurrentRequestHeader().getSender(); if ( sender != null && !sender.isEmpty() ) { return sender + "." + name; } else { return name; } } |
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.
1 2 3 |
public interface SenderAware { public void setSender(String sender); } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class HelloWordServiceImpl implements HelloWordService, SenderAware { private String sender; @Override public void setSender(String sender) { this.sender = sender; } @Override public String getGreetings(Person person) { String message = "Hi"; if ( sender.equalsIgnoreCase("sender2" ) ) { message = "Hello"; } return message + " " + person.getName() + " " + person.getSurname(); } } |
applicationContext.xml
con scope sender
.
1 2 3 4 5 6 7 8 9 10 11 |
<!-- Definiscono lo scope "sender"--> <bean id="senderScope" class="it.javaboss.scope.SenderScope"/> <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="sender" value-ref="senderScope"/> </map> </property> </bean> <!-- Definisco il sender scoped bean --> <bean id="helloService" class="it.javaboss.service.HelloWordServiceImpl" 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.