Guide rapide des lunettes Spring Bean

1. Vue d'ensemble

Dans ce tutoriel rapide, vous découvrirez les différents types d'étendues de bean dans le framework Spring.

La portée d'un bean définit le cycle de vie et la visibilité de ce bean dans les contextes dans lesquels il est utilisé.

La dernière version du framework Spring définit 6 types d'étendues:

  • singleton
  • prototype
  • demande
  • session
  • application
  • Websocket

Les quatre dernières portées mentionnées requête, session, application et websocket ne sont disponibles que dans une application Web.

2. Portée Singleton

Définir un bean avec une portée singleton signifie que le conteneur crée une seule instance de ce bean, et toutes les demandes pour ce nom de bean renverront le même objet, qui est mis en cache. Toutes les modifications apportées à l'objet seront reflétées dans toutes les références au bean. Cette étendue est la valeur par défaut si aucune autre étendue n'est spécifiée.

Créons une entité Person pour illustrer le concept d'étendues:

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

Ensuite, nous définissons le bean avec une portée singleton en utilisant l' annotation @Scope :

@Bean @Scope("singleton") public Person personSingleton() { return new Person(); }

Nous pouvons également utiliser une constante au lieu de la valeur String de la manière suivante:

@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)

Nous procédons maintenant à l'écriture d'un test qui montre que deux objets faisant référence au même bean auront les mêmes valeurs, même si un seul d'entre eux change d'état, car ils référencent tous les deux la même instance de 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(); }

Le fichier scopes.xml de cet exemple doit contenir les définitions xml des beans utilisés:

3. Portée du prototype

Un bean avec une portée prototype renverra une instance différente à chaque fois qu'il est demandé au conteneur. Il est défini en définissant la valeur prototype sur l' annotation @Scope dans la définition du bean:

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

Nous pourrions également utiliser une constante comme nous l'avons fait pour la portée singleton:

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)

Nous allons maintenant écrire un test similaire au précédent qui montre que deux objets demandant le même nom de bean avec le prototype de portée auront des états différents, car ils ne font plus référence à la même instance de 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(); } 

Le fichier scopes.xml est similaire à celui présenté dans la section précédente en ajoutant la définition xml pour le bean avec la portée prototype :

4. Étendues Web Aware

Comme mentionné, il existe quatre étendues supplémentaires qui ne sont disponibles que dans un contexte d'application Web. Ceux-ci sont moins souvent utilisés dans la pratique.

La portée de la requête crée une instance de bean pour une seule requête HTTP tandis que la portée de la session crée une session HTTP.

La portée d' application crée l'instance de bean pour le cycle de vie d'un ServletContext et la portée websocket la crée pour une session WebSocket particulière .

Créons une classe à utiliser pour instancier les beans:

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

4.1. Champ d'application de la demande

Nous pouvons définir le bean avec la portée de la requête en utilisant l' annotation @Scope :

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

L' attribut proxyMode est nécessaire car, au moment de l'instanciation du contexte de l'application Web, il n'y a pas de requête active. Spring créera un proxy à injecter en tant que dépendance et instanciera le bean cible lorsqu'il est nécessaire dans une requête.

Nous pouvons également utiliser une annotation composée @RequestScope qui agit comme un raccourci pour la définition ci-dessus:

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

Ensuite, nous pouvons définir un contrôleur qui a une référence injectée au requestScopedBean . Nous devons accéder à la même demande deux fois afin de tester les portées spécifiques au Web.

Si nous affichons le message chaque fois que la requête est exécutée, nous pouvons voir que la valeur est réinitialisée à null , même si elle est modifiée ultérieurement dans la méthode. Cela est dû au fait qu'une instance de bean différente est renvoyée pour chaque requête.

@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. Portée de la session

Nous pouvons définir le bean avec la portée de la session de la même manière:

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

Il existe également une annotation composée dédiée que nous pouvons utiliser pour simplifier la définition du 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

Nous avons démontré différentes portées de haricots fournies par Spring et leurs utilisations prévues.

L'implémentation de ce didacticiel se trouve dans le projet GitHub - il s'agit d'un projet basé sur Eclipse, il devrait donc être facile à importer et à exécuter tel quel.