Kurzanleitung zu Spring Bean Scopes

1. Übersicht

In diesem kurzen Tutorial lernen Sie die verschiedenen Arten von Bean-Scopes im Spring-Framework kennen.

Der Umfang einer Bean definiert den Lebenszyklus und die Sichtbarkeit dieser Bean in den Kontexten, in denen sie verwendet wird.

Die neueste Version von Spring Framework definiert 6 Arten von Bereichen:

  • Singleton
  • Prototyp
  • Anfrage
  • Session
  • Anwendung
  • Websocket

Die letzten vier Bereiche, in denen Anforderung, Sitzung, Anwendung und Websocket erwähnt wurden, sind nur in einer webfähigen Anwendung verfügbar.

2. Singleton Scope

Das Definieren einer Bean mit Singleton- Bereich bedeutet, dass der Container eine einzelne Instanz dieser Bean erstellt und alle Anforderungen für diesen Bean-Namen dasselbe Objekt zurückgeben, das zwischengespeichert wird. Alle Änderungen am Objekt werden in allen Verweisen auf die Bean berücksichtigt. Dieser Bereich ist der Standardwert, wenn kein anderer Bereich angegeben ist.

Erstellen wir eine Personenentität , um das Konzept der Bereiche zu veranschaulichen:

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

Anschließend definieren wir die Bean mit Singleton- Bereich mithilfe der Annotation @Scope :

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

Wir können auch eine Konstante anstelle des String- Werts folgendermaßen verwenden:

@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)

Jetzt schreiben wir einen Test, der zeigt, dass zwei Objekte, die auf dieselbe Bean verweisen, dieselben Werte haben, auch wenn nur eines von ihnen seinen Status ändert, da beide auf dieselbe Bean-Instanz verweisen:

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(); }

Die Datei scopes.xml in diesem Beispiel sollte die XML-Definitionen der verwendeten Beans enthalten:

3. Umfang des Prototyps

Eine Bean mit Prototypbereich gibt jedes Mal eine andere Instanz zurück, wenn sie vom Container angefordert wird. Es wird , indem der Wert definiert Prototyp auf die @Scope Annotation in der Bean - Definition:

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

Wir könnten auch eine Konstante verwenden, wie wir es für den Singleton-Bereich getan haben:

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)

Wir werden jetzt einen ähnlichen Test wie zuvor schreiben, der zeigt, dass zwei Objekte, die denselben Bean-Namen mit Scope-Prototyp anfordern, unterschiedliche Zustände haben, da sie sich nicht mehr auf dieselbe Bean-Instanz beziehen:

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(); } 

Die Datei scopes.xml ähnelt der im vorherigen Abschnitt beschriebenen Datei, während die XML-Definition für die Bean mit dem Prototypbereich hinzugefügt wird :

4. Web Aware Scopes

Wie bereits erwähnt, gibt es vier zusätzliche Bereiche, die nur in einem webfähigen Anwendungskontext verfügbar sind. Diese werden in der Praxis seltener eingesetzt.

Der Anforderungsbereich erstellt eine Bean-Instanz für eine einzelne HTTP-Anforderung, während der Sitzungsbereich für eine HTTP-Sitzung erstellt wird.

Der Anwendungsbereich erstellt die Bean-Instanz für den Lebenszyklus eines ServletContext und der Websocket- Bereich erstellt sie für eine bestimmte WebSocket- Sitzung.

Erstellen wir eine Klasse zum Instanziieren der Beans:

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

4.1. Umfang anfordern

Wir können die Bean mit dem Anforderungsbereich mithilfe der Annotation @Scope definieren :

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

Das Attribut proxyMode ist erforderlich, da zum Zeitpunkt der Instanziierung des Webanwendungskontexts keine aktive Anforderung vorhanden ist. Spring erstellt einen Proxy, der als Abhängigkeit eingefügt werden soll, und instanziiert die Ziel-Bean, wenn sie in einer Anforderung benötigt wird.

Wir können auch eine von @RequestScope zusammengesetzte Annotation verwenden, die als Verknüpfung für die obige Definition dient:

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

Als nächstes können wir einen Controller definieren, der einen injizierten Verweis auf die requestScopedBean hat . Wir müssen zweimal auf dieselbe Anfrage zugreifen, um die webspezifischen Bereiche zu testen.

Wenn wir die Nachricht jedes Mal anzeigen , wenn die Anforderung ausgeführt wird, können wir sehen, dass der Wert auf null zurückgesetzt wird , obwohl er später in der Methode geändert wird. Dies liegt daran, dass für jede Anforderung eine andere Bean-Instanz zurückgegeben wird.

@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. Sitzungsumfang

Wir können die Bean mit Sitzungsumfang auf ähnliche Weise definieren:

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

Es gibt auch eine spezielle zusammengesetzte Anmerkung, mit der wir die Bean-Definition vereinfachen können:

@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

Wir haben verschiedene von Spring bereitgestellte Bohnenbereiche und deren Verwendungszweck demonstriert.

Die Implementierung dieses Tutorials finden Sie im GitHub-Projekt - dies ist ein Eclipse-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein.