Panoramica
In questo tutorial veloce, impareremo i diversi tipi di scopi dei bean nel framework Spring.
Lo scopo di un bean definisce il ciclo di vita e la visibilità di quel bean nei contesti in cui lo usiamo.
L’ultima versione del framework Spring definisce 6 tipi di scope:
- singleton
- prototipo
- request
- session
- application
- websocket
Gli ultimi quattro scope menzionati, request, session, application e websocket, sono disponibili solo in un’applicazione web-aware.
Altra lettura:
Cos’è uno Spring Bean?
Le annotazioni di Spring Bean
Singleton Scope
Quando definiamo un bean con lo scope singleton, il contenitore crea una singola istanza di quel bean; tutte le richieste per quel nome di bean restituiranno lo stesso oggetto, che viene salvato nella cache. Qualsiasi modifica all’oggetto si rifletterà in tutti i riferimenti al bean. Questo ambito è il valore predefinito se non viene specificato nessun altro ambito.
Creiamo un’entità Person per esemplificare il concetto di scope:
public class Person { private String name; // standard constructor, getters and setters}
Poi, definiamo il bean con lo scope singleton usando l’annotazione @Scope:
@Bean@Scope("singleton")public Person personSingleton() { return new Person();}
Possiamo anche usare una costante invece del valore String nel modo seguente:
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
Ora possiamo procedere a scrivere un test che mostri che due oggetti che fanno riferimento allo stesso bean avranno gli stessi valori, anche se solo uno di loro cambia il proprio stato, dato che entrambi fanno riferimento alla stessa istanza del bean:
private static final String NAME = "John Smith";@Testpublic void givenSingletonScope_whenSetName_thenEqualNames() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("scopes.xml"); Person personSingletonA = (Person) applicationContext.getBean("personSingleton"); Person personSingletonB = (Person) applicationContext.getBean("personSingleton"); personSingletonA.setName(NAME); Assert.assertEquals(NAME, personSingletonB.getName()); ((AbstractApplicationContext) applicationContext).close();}
Il file scopes.xml in questo esempio dovrebbe contenere le definizioni xml dei bean usati:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="org.baeldung.scopes.Person" scope="singleton"/> </beans>
Prototype Scope
Un bean con lo scope prototype restituirà un’istanza diversa ogni volta che viene richiesto dal contenitore. Si definisce impostando il valore prototype all’annotazione @Scope nella definizione del bean:
@Bean@Scope("prototype")public Person personPrototype() { return new Person();}
Possiamo anche usare una costante come abbiamo fatto per lo scope singleton:
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Scriviamo ora un test simile a quello precedente che mostra due oggetti che richiedono lo stesso nome del bean con lo scope prototype. Avranno stati diversi perché non si riferiscono più alla stessa istanza del bean:
private static final String NAME = "John Smith";private static final String NAME_OTHER = "Anna Jones";@Testpublic void givenPrototypeScope_whenSetNames_thenDifferentNames() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("scopes.xml"); Person personPrototypeA = (Person) applicationContext.getBean("personPrototype"); Person personPrototypeB = (Person) applicationContext.getBean("personPrototype"); personPrototypeA.setName(NAME); personPrototypeB.setName(NAME_OTHER); Assert.assertEquals(NAME, personPrototypeA.getName()); Assert.assertEquals(NAME_OTHER, personPrototypeB.getName()); ((AbstractApplicationContext) applicationContext).close();}
Il file scopes.xml è simile a quello presentato nella sezione precedente, mentre si aggiunge la definizione xml per il bean con lo scope prototipo:
<bean class="org.baeldung.scopes.Person" scope="prototype"/>
Web Aware Scopes
Come già detto, ci sono quattro scope aggiuntivi che sono disponibili solo in un contesto applicativo web-aware. Li usiamo meno spesso in pratica.
L’ambito request crea un’istanza del bean per una singola richiesta HTTP, mentre l’ambito session crea un’istanza del bean per una sessione HTTP.
L’application scope crea l’istanza del bean per il ciclo di vita di un ServletContext, e il websocket scope lo crea per una particolare sessione WebSocket.
Creiamo una classe da usare per istanziare i beans:
public class HelloMessageGenerator { private String message; // standard getter and setter}
4.1. Request Scope
Possiamo definire il bean con il request scope usando l’annotazione @Scope:
@Bean@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)public HelloMessageGenerator requestScopedBean() { return new HelloMessageGenerator();}
L’attributo proxyMode è necessario perché al momento dell’istanziazione del contesto dell’applicazione web, non c’è nessuna richiesta attiva. Spring crea un proxy da iniettare come dipendenza, e istanzia il bean di destinazione quando è necessario in una richiesta.
Possiamo anche usare un’annotazione composta @RequestScope che funge da scorciatoia per la definizione precedente:
@Bean@RequestScopepublic HelloMessageGenerator requestScopedBean() { return new HelloMessageGenerator();}
In seguito possiamo definire un controller che ha un riferimento iniettato al requestScopedBean. Abbiamo bisogno di accedere due volte alla stessa richiesta per testare gli scope specifici del web.
Se visualizziamo il messaggio ogni volta che la richiesta viene eseguita, possiamo vedere che il valore viene resettato a null, anche se poi viene modificato nel metodo. Questo è dovuto al fatto che un’istanza diversa del bean viene restituita per ogni richiesta.
@Controllerpublic class ScopesController { @Resource(name = "requestScopedBean") HelloMessageGenerator requestScopedBean; @RequestMapping("/scopes/request") public String getRequestScopeMessage(final Model model) { model.addAttribute("previousMessage", requestScopedBean.getMessage()); requestScopedBean.setMessage("Good morning!"); model.addAttribute("currentMessage", requestScopedBean.getMessage()); return "scopesExample"; }}
4.2. Session Scope
Possiamo definire il bean con lo scope di sessione in modo simile:
@Bean@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)public HelloMessageGenerator sessionScopedBean() { return new HelloMessageGenerator();}
C’è anche un’annotazione composta dedicata che possiamo usare per semplificare la definizione del bean:
@Bean@SessionScopepublic HelloMessageGenerator sessionScopedBean() { return new HelloMessageGenerator();}
In seguito definiamo un controller con un riferimento al sessionScopedBean. Di nuovo, abbiamo bisogno di eseguire due richieste per mostrare che il valore del campo message è lo stesso per la sessione.
In questo caso, quando la richiesta viene fatta per la prima volta, il valore message è nullo. Tuttavia, una volta che viene cambiato, quel valore viene mantenuto per le richieste successive, poiché la stessa istanza del bean viene restituita per l’intera sessione.
@Controllerpublic class ScopesController { @Resource(name = "sessionScopedBean") HelloMessageGenerator sessionScopedBean; @RequestMapping("/scopes/session") public String getSessionScopeMessage(final Model model) { model.addAttribute("previousMessage", sessionScopedBean.getMessage()); sessionScopedBean.setMessage("Good afternoon!"); model.addAttribute("currentMessage", sessionScopedBean.getMessage()); return "scopesExample"; }}
4.3. Application Scope
L’application scope crea l’istanza del bean per il ciclo di vita di un ServletContext.
Questo è simile al singleton scope, ma c’è una differenza molto importante per quanto riguarda lo scope del bean.
Quando i bean sono application scoped, la stessa istanza del bean è condivisa tra più applicazioni basate su servlet in esecuzione nello stesso ServletContext, mentre i singleton scoped sono scoped solo per un singolo contesto applicativo.
Creiamo il bean con l’application scope:
@Bean@Scope( value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)public HelloMessageGenerator applicationScopedBean() { return new HelloMessageGenerator();}
Analogamente agli scope di richiesta e di sessione, possiamo usare una versione più breve:
@Bean@ApplicationScopepublic HelloMessageGenerator applicationScopedBean() { return new HelloMessageGenerator();}
Creiamo ora un controller che faccia riferimento a questo bean:
@Controllerpublic class ScopesController { @Resource(name = "applicationScopedBean") HelloMessageGenerator applicationScopedBean; @RequestMapping("/scopes/application") public String getApplicationScopeMessage(final Model model) { model.addAttribute("previousMessage", applicationScopedBean.getMessage()); applicationScopedBean.setMessage("Good afternoon!"); model.addAttribute("currentMessage", applicationScopedBean.getMessage()); return "scopesExample"; }}
In questo caso, una volta impostato nell’applicationScopedBean, il valore del messaggio sarà mantenuto per tutte le richieste successive, per le sessioni e anche per diverse applicazioni servlet che accederanno a questo bean, a condizione che sia in esecuzione nello stesso ServletContext.
4.4. Ambito WebSocket
Finalmente, creiamo il bean con l’ambito websocket:
@Bean@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)public HelloMessageGenerator websocketScopedBean() { return new HelloMessageGenerator();}
Quando si accede per la prima volta, i bean con ambito WebSocket sono memorizzati negli attributi della sessione WebSocket. La stessa istanza del bean viene poi restituita ogni volta che si accede a quel bean durante l’intera sessione WebSocket.
Possiamo anche dire che esibisce un comportamento singleton, ma limitato solo ad una sessione WebSocket.
Conclusione
In questo articolo, abbiamo discusso i diversi scope bean forniti da Spring e quali sono i loro usi previsti.
L’implementazione di questo articolo può essere trovata nel progetto GitHub.
Inizia con Spring 5 e Spring Boot 2, attraverso il corso Learn Spring:
>> IL CORSO