Spring Bean Scopes
Spring Bean Scopes ermöglichen es uns, eine feinere Kontrolle über die Erstellung von Bean-Instanzen zu haben. Manchmal möchten wir eine Bean-Instanz als Singleton erstellen, aber in anderen Fällen möchten wir vielleicht, dass sie bei jeder Anfrage oder einmal in einer Sitzung erstellt wird.
Arten von Spring Bean Scopes
Es gibt fünf Arten von Spring Bean Scopes:
- singleton – es wird nur eine Instanz des Spring Beans für den Spring-Container erstellt. Dies ist der Standard Spring Bean Scope. Wenn Sie diesen Scope verwenden, stellen Sie sicher, dass der Bean keine gemeinsam genutzten Instanzvariablen hat, da dies zu Dateninkonsistenzproblemen führen könnte.
- prototype – jedes Mal, wenn der Bean vom Spring-Container angefordert wird, wird eine neue Instanz erstellt.
- request – Dies entspricht dem Prototype-Scope, ist jedoch für Webanwendungen gedacht. Für jede HTTP-Anfrage wird eine neue Instanz des Beans erstellt.
- session – Für jede HTTP-Sitzung wird vom Container ein neuer Bean erstellt.
- global-session – Dies wird verwendet, um globale Session-Beans für Portlet-Anwendungen zu erstellen.
Spring Bean Singleton und Prototype Scope
Spring Bean Singleton und Prototype Scopes können in eigenständigen Spring-Anwendungen verwendet werden. Sehen wir uns an, wie wir diese Scopes mit der @Scope-Anmerkung einfach konfigurieren können. Nehmen wir an, wir haben eine Java Bean Klasse.
package com.journaldev.spring;
public class MyBean {
public MyBean() {
System.out.println("MyBean-Instanz erstellt");
}
}
Definieren wir die Spring-Konfigurationsklasse, wo wir die Methode definieren, um die MyBean-Instanz vom Spring-Container zu bekommen.
package com.journaldev.spring;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class MyConfiguration {
@Bean
@Scope(value="singleton")
public MyBean myBean() {
return new MyBean();
}
}
Beachten Sie, dass Singleton der Standard-Scope ist, also können wir @Scope(value=“singleton“) aus der obigen Bean-Definition entfernen. Jetzt erstellen wir eine Hauptmethode und testen den Singleton-Scope.
package com.journaldev.spring;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MySpringApp {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(MyConfiguration.class);
ctx.refresh();
MyBean mb1 = ctx.getBean(MyBean.class);
System.out.println(mb1.hashCode());
MyBean mb2 = ctx.getBean(MyBean.class);
System.out.println(mb2.hashCode());
ctx.close();
}
}
Wenn das obige Programm ausgeführt wird, erhalten wir eine Ausgabe wie unten.
MyBean-Instanz erstellt
867988177
867988177
Beachten Sie, dass beide MyBean-Instanzen denselben Hashcode haben und der Konstruktor einmal aufgerufen wird, was bedeutet, dass der Spring-Container immer dieselbe Instanz von MyBean zurückgibt. Ändern wir jetzt den Scope zu Prototype.
@Bean
@Scope(value="prototype")
public MyBean myBean() {
return new MyBean();
}
Diesmal erhalten wir die folgende Ausgabe, wenn die Hauptmethode ausgeführt wird.
MyBean-Instanz erstellt
867988177
MyBean-Instanz erstellt
443934570
Es ist klar, dass die MyBean-Instanz jedes Mal erstellt wird, wenn sie vom Spring-Container angefordert wird. Ändern wir nun den Scope zu Request.
@Bean
@Scope(value="request")
public MyBean myBean() {
return new MyBean();
}
In diesem Fall erhalten wir die folgende Ausnahme.
Exception in thread "main" java.lang.IllegalStateException: No Scope registered for scope name 'request'
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:347)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:224)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1015)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:334)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1107)
at com.journaldev.spring.MySpringApp.main(MySpringApp.java:12)
Das liegt daran, dass die Scopes Request, Session und Global-Session nicht für eigenständige Anwendungen verfügbar sind.
Spring Bean Request und Session Scope
Für das Beispiel des Spring Bean Request und Session Scope werden wir eine Spring Boot-Webanwendung erstellen. Erstellen Sie ein Spring Boot-Starter-Projekt und wählen Sie „Web“, damit wir es als Webanwendung ausführen können.
Unser fertiges Projekt wird wie das folgende Bild aussehen.
ServletInitializer und SpringBootMvcApplication sind automatisch generierte Spring Boot-Klassen. Wir müssen dort keine Änderungen vornehmen. Hier ist meine pom.xml-Datei, sehen Sie sich die Abhängigkeiten für unsere Anwendung an. Ihre pom.xml-Datei könnte leicht unterschiedlich sein, abhängig von der Eclipse-Version, die Sie verwenden.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev.spring</groupId>
<artifactId>Spring-Boot-MVC</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>Spring-Boot-MVC</name>
<description>Spring Beans Scope MVC</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>10</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Spring Bean Request Scope
package com.journaldev.spring;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class DataRequestScope {
private String name = "Request Scope";
public DataRequestScope() {
System.out.println("DataRequestScope Konstruktor aufgerufen");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Spring Bean Session Scope
package com.journaldev.spring;
import org.springframework.context.annotation.Scope;
import java.time.LocalDateTime;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class DataSessionScope {
private String name = "Session Scope";
public DataSessionScope() {
System.out.println("DataSessionScope Konstruktor aufgerufen um "+LocalDateTime.now());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Spring Komponente
Jetzt erstellen wir eine Spring-Komponente und verwenden Spring, um die oben genannten Beans automatisch zu konfigurieren.
package com.journaldev.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Customer {
@Autowired
private DataRequestScope dataRequestScope;
@Autowired
private DataSessionScope dataSessionScope;
public DataRequestScope getDataRequestScope() {
return dataRequestScope;
}
public void setDataRequestScope(DataRequestScope dataRequestScope) {
this.dataRequestScope = dataRequestScope;
}
public DataSessionScope getDataSessionScope() {
return dataSessionScope;
}
public void setDataSessionScope(DataSessionScope dataSessionScope) {
this.dataSessionScope = dataSessionScope;
}
}
Spring Rest Controller
Abschließend erstellen wir eine RestController-Klasse und konfigurieren einige API-Endpunkte für unsere Testzwecke.
package com.journaldev.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloData {
@Autowired
private Customer customer;
@RequestMapping("/nameRS")
public String helloRS() {
return customer.getDataRequestScope().getName();
}
@RequestMapping("/nameSSUpdated")
public String helloSSUpdated() {
customer.getDataSessionScope().setName("Session Scope Aktualisiert");
return customer.getDataSessionScope().getName();
}
@RequestMapping("/nameSS")
public String helloSS() {
return customer.getDataSessionScope().getName();
}
}
Spring Boot Session Timeout Konfiguration
Schließlich müssen wir die Spring Boot Session Timeout Variablen konfigurieren, fügen Sie die folgenden Eigenschaften in src/main/resources/application.properties ein.
server.session.cookie.max-age= 1
server.session.timeout= 1
Jetzt werden unsere Spring Beans mit Session Scope in einer Minute ungültig. Führen Sie einfach die Klasse SpringBootMvcApplication als Spring Boot-Anwendung aus. Sie sollten die folgende Ausgabe für unsere konfigurierten Endpunkte sehen.
2018-05-23 17:02:25.830 INFO 6921 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/nameRS]}" onto public java.lang.String com.journaldev.spring.HelloData.helloRS()
2018-05-23 17:02:25.831 INFO 6921 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/nameSSUpdated]}" onto public java.lang.String com.journaldev.spring.HelloData.helloSSUpdated()
2018-05-23 17:02:25.832 INFO 6921 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/nameSS]}" onto public java.lang.String com.journaldev.spring.HelloData.helloSS()
Spring Bean Request Scope Test
Öffnen Sie einen beliebigen Browser und gehen Sie zur URL https://localhost:8080/nameRS und überprüfen Sie die Konsolenausgabe. Sie sollten sehen, dass „DataRequestScope Konstruktor aufgerufen“ bei jeder Anfrage gedruckt wird.
Spring Bean Session Scope Test
Gehen Sie zu https://localhost:8080/nameSS und Sie sollten die folgende Ausgabe erhalten.
Gehen Sie jetzt zu https://localhost:8080/nameSSUpdated, damit der DataSessionScope Name auf „Session Scope Aktualisiert“ geändert wird.
Gehen Sie jetzt erneut zu https://localhost:8080/nameSS und Sie sollten den aktualisierten Wert sehen.
Bis zu diesem Zeitpunkt sollten Sie sehen, dass „DataSessionScope Konstruktor aufgerufen um XXX“ nur einmal in der Konsolenausgabe erscheint. Warten Sie jetzt 1 Minute, damit unser session-scoped Bean ungültig wird. Gehen Sie dann wieder zu https://localhost:8080/nameSS und Sie sollten die ursprüngliche Ausgabe sehen. Überprüfen Sie auch die Konsolenmeldung für die erneute Erstellung von DataSessionScope durch den Container.
Fazit
Das ist alles für das Tutorial zu Spring Beans Scopes. Die Erforschung verschiedener Scopes wie Singleton, Prototype, Request und Session hilft dabei zu verstehen, wie Spring die Lebenszyklus und Abhängigkeiten von Beans verwaltet. Es ist wichtig, den richtigen Scope für jeden Bean zu wählen, um eine effiziente Speicherverwaltung und Anwendungsleistung zu gewährleisten – eine Anleitung.