Spring Bean Scopes 允许我们更精细地控制 bean 实例的创建。有时我们希望将 bean 实例创建为单例,但在其他一些情况下,我们可能希望在每次请求时或在会话中创建一次。
有五种类型春豆 scopes:
-
单例- 只会为 spring 容器创建一个 spring bean 实例。这是默认的 spring bean 范围。使用此作用域时,请确保 bean 没有共享实例变量,否则可能会导致数据不一致问题。
-
原型– 每次从 spring 容器请求 bean 时,都会创建一个新实例。
-
request– 这与原型范围相同,但它旨在用于 Web 应用程序。将为每个 HTTP 请求创建一个新的 bean 实例。
-
session– 容器将为每个 HTTP 会话创建一个新的 bean。
-
全球会议– 这用于为 Portlet 应用程序创建全局会话 Bean。
Spring bean 单例和原型范围可以在独立的 spring 应用程序中使用。让我们看看如何使用轻松配置这些范围@Scope
注解。假设我们有一个 java bean 类。
package com.journaldev.spring;
public class MyBean {
public MyBean() {
System.out.println("MyBean instance created");
}
}
让我们定义弹簧配置类,我们将在其中定义从 spring 容器获取 MyBean 实例的方法。
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();
}
}
注意singleton
是默认范围,所以我们可以删除@Scope(value="singleton")
从上面的bean定义。现在让我们创建一个 main 方法并测试单例范围。
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();
}
}
当执行上面的程序时,我们将得到如下输出。
MyBean instance created
867988177
867988177
请注意,两个 MyBean 实例具有相同的哈希码,并且构造函数被调用一次,这意味着 spring 容器始终返回相同的 MyBean 实例。现在我们将范围更改为prototype
.
@Bean
@Scope(value="prototype")
public MyBean myBean() {
return new MyBean();
}
这次执行 main 方法时我们将得到以下输出。
MyBean instance created
867988177
MyBean instance created
443934570
很明显,每次从 Spring 容器请求时都会创建 MyBean 实例。现在我们将范围更改为request
.
@Bean
@Scope(value="request")
public MyBean myBean() {
return new MyBean();
}
在这种情况下,我们将得到以下异常。
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)
这是因为request
, session
and global-session
范围不适用于独立应用程序。
For spring bean request and session scope example, we will create Spring Boot web application. Create a spring boot starter project and choose “web” so that we can run it as a web application. Our final project will look like below image. ServletInitializer
and SpringBootMvcApplication
are auto generated spring boot classes. We don’t need to make any changes there. Here is my pom.xml file, have a look at the dependencies for our application. Your pom.xml file might be slightly different based on the Eclipse version you are using.
<?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 组件,并将它们配置为 spring 容器中的 spring bean,范围为request
and session
.
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 Constructor Called");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
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 Constructor Called at "+LocalDateTime.now());
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
现在让我们创建一个 spring 组件并使用 spring 自动配置上面的 beans。
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;
}
}
最后,让我们创建一个 RestController 类并配置一些 API 端点以用于测试目的。
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 Updated");
return customer.getDataSessionScope().getName();
}
@RequestMapping("/nameSS")
public String helloSS() {
return customer.getDataSessionScope().getName();
}
}
最后我们必须配置 Spring Boot 会话超时变量,添加以下属性src/main/resources/application.properties
.
server.session.cookie.max-age= 1
server.session.timeout= 1
现在我们的具有会话范围的 Spring beans 将在一分钟内失效。只需运行SpringBootMvcApplication
类作为 Spring Boot 应用程序。您应该看到下面我们配置的端点的输出。
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()
打开任何浏览器并转到 URLhttps://localhost:8080/nameRS
并检查控制台输出。你应该看到DataRequestScope Constructor Called
根据每个请求打印。
Go to https://localhost:8080/nameSS
and you would get following output. Now go to https://localhost:8080/nameSSUpdated
so that DataSessionScope
name value is updated to Session Scope Updated
. Now again go to https://localhost:8080/nameSS
and you should see the updated value. By this time, you should see DataSessionScope Constructor Called at XXX
only once in the console output. Now wait for 1 minute so that our session scoped bean is invalidated. Then again go to https://localhost:8080/nameSS
and you should see the original output. Also you should check console message for creation of DataSessionScope again by the container. That’s all for spring beans scope tutorial.
您可以从我们的网站下载 Spring Beans Scope Spring Boot 项目GitHub 存储库.