Guida rapida agli ambiti di Spring Bean

1. Panoramica

In questo breve tutorial imparerai a conoscere i diversi tipi di bean scopes nel framework Spring.

L'ambito di un bean definisce il ciclo di vita e la visibilità di quel bean nei contesti in cui viene utilizzato.

L'ultima versione del framework Spring definisce 6 tipi di ambiti:

  • singleton
  • prototipo
  • richiesta
  • sessione
  • applicazione
  • websocket

Gli ultimi quattro ambiti menzionati richiesta, sessione, applicazione e websocket sono disponibili solo in un'applicazione web-aware.

2. Singleton Scope

La definizione di un bean con ambito singleton significa che il contenitore crea una singola istanza di quel bean e tutte le richieste per quel nome di bean restituiranno lo stesso oggetto, che è memorizzato nella cache. Qualsiasi modifica all'oggetto si rifletterà in tutti i riferimenti al bean. Questo ambito è il valore predefinito se non viene specificato alcun altro ambito.

Creiamo un'entità Person per esemplificare il concetto di ambiti:

public class Person { private String name; // standard constructor, getters and setters }

Successivamente, definiamo il bean con ambito singleton utilizzando 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 procediamo a scrivere un test che mostri che due oggetti che fanno riferimento allo stesso bean avranno gli stessi valori, anche se solo uno di essi cambia il loro stato, poiché entrambi fanno riferimento alla stessa istanza di bean:

private static final String NAME = "John Smith"; @Test public 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 utilizzati:

3. Portata del prototipo

Un bean con ambito prototipo restituirà un'istanza diversa ogni volta che viene richiesto dal contenitore. Viene definito impostando il prototipo del valore sull'annotazione @Scope nella definizione del bean:

@Bean @Scope("prototype") public Person personPrototype() { return new Person(); }

Potremmo anche usare una costante come abbiamo fatto per l'ambito singleton:

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)

Scriveremo ora un test simile a quello precedente che mostra due oggetti che richiedono lo stesso nome di bean con un prototipo di ambito avranno stati diversi, poiché non si riferiscono più alla stessa istanza di bean:

private static final String NAME = "John Smith"; private static final String NAME_OTHER = "Anna Jones"; @Test public 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 durante l'aggiunta della definizione xml per il bean con ambito prototipo :

4. Ambiti consapevoli del Web

Come accennato, esistono quattro ambiti aggiuntivi disponibili solo in un contesto di applicazione compatibile con il Web. Questi sono usati meno spesso nella pratica.

La richiesta di applicazione crea un'istanza di bean per una singola richiesta HTTP mentre s ESSIONE portata crea per una sessione HTTP.

L' ambito dell'applicazione crea l'istanza del bean per il ciclo di vita di un ServletContext e l' ambito websocket la crea per una particolare sessione WebSocket .

Creiamo una classe da utilizzare per istanziare i bean:

public class HelloMessageGenerator { private String message; // standard getter and setter }

4.1. Ambito della richiesta

Possiamo definire il bean con l' ambito della richiesta utilizzando 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 della creazione dell'istanza del contesto dell'applicazione web, non è presente alcuna richiesta attiva. Spring creerà un proxy da iniettare come dipendenza e istanzerà il bean di destinazione quando è necessario in una richiesta.

Possiamo anche usare un'annotazione composta da @RequestScope che funge da scorciatoia per la definizione di cui sopra:

@Bean @RequestScope public HelloMessageGenerator requestScopedBean() { return new HelloMessageGenerator(); }

Successivamente, possiamo definire un controller che ha un riferimento iniettato a requestScopedBean . È necessario accedere alla stessa richiesta due volte per testare gli ambiti specifici del Web.

Se visualizziamo il messaggio ogni volta che viene eseguita la richiesta, possiamo vedere che il valore viene reimpostato su null , anche se successivamente viene modificato nel metodo. Ciò è dovuto alla restituzione di un'istanza di bean diversa per ciascuna richiesta.

@Controller public 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. Ambito della sessione

Possiamo definire il bean con l' ambito della 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 @SessionScope public HelloMessageGenerator sessionScopedBean() { return new HelloMessageGenerator(); }

Next, we define a controller with a reference to the sessionScopedBean. Again, we need to run two requests in order to show that the value of the message field is the same for the session.

In this case, when the request is made for the first time, the value message is null. But once, it is changed, then that value is retained for subsequent requests as the same instance of the bean is returned for the entire session.

@Controller public 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

The application scope creates the bean instance for the lifecycle of a ServletContext.

This is similar to the singleton scope but there is a very important difference with regards to the scope of the bean.

When beans are application scoped the same instance of the bean is shared across multiple servlet-based applications running in the same ServletContext, while singleton-scoped beans are scoped to a single application context only.

Let's create the bean with application scope:

@Bean @Scope( value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS) public HelloMessageGenerator applicationScopedBean() { return new HelloMessageGenerator(); }

Analogously as for the request and session scopes, we can use a shorter version:

@Bean @ApplicationScope public HelloMessageGenerator applicationScopedBean() { return new HelloMessageGenerator(); }

Now, let's create a controller that references this bean:

@Controller public 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 this case, value message once set in the applicationScopedBean will be retained for all subsequent requests, sessions and even for a different servlet application that will access this bean, provided it is running in the same ServletContext.

4.4. WebSocket Scope

Finally, let's create the bean with websocket scope:

@Bean @Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS) public HelloMessageGenerator websocketScopedBean() { return new HelloMessageGenerator(); }

WebSocket-scoped beans when first accessed are stored in the WebSocket session attributes. The same instance of the bean is then returned whenever that bean is accessed during the entire WebSocket session.

We can also say that it exhibits singleton behavior but limited to a WebSocket session only.

5. Conclusion

Abbiamo dimostrato diversi ambiti di bean forniti da Spring e quali sono i loro usi previsti.

L'implementazione di questo tutorial può essere trovata nel progetto GitHub: questo è un progetto basato su Eclipse, quindi dovrebbe essere facile da importare ed eseguire così com'è.