Spring Bean 作用域
Spring Bean Scopes 允许我们对 bean 实例的创建进行更精细的控制。有时我们希望将 bean 实例创建为单例,但在其他情况下,我们可能希望它在每个请求或会话中创建一次。
Spring Bean 范围
Spring bean的作用域有五种类型:
- 单例- 只会为 Spring 容器创建一个 Spring bean 实例。这是默认的 Spring bean 作用域。使用此作用域时,请确保 bean 没有共享实例变量,否则可能会导致数据不一致问题。
- prototype – 每次从 spring 容器请求 bean 时都会创建一个新实例。
- request – 与原型范围相同,但它适用于 Web 应用程序。每次 HTTP 请求都会创建一个新的 bean 实例。
- session – 容器会为每个 HTTP 会话创建一个新的 bean。
- global-session—用于为 Portlet 应用程序创建全局会话 Bean。
Spring Bean 单例和原型范围
Spring bean 单例和原型范围可用于独立的 spring 应用程序中。让我们看看如何使用@Scope
注释轻松配置这些范围。假设我们有一个 java bean 类。
package com.journaldev.spring;
public class MyBean {
public MyBean() {
System.out.println("MyBean instance created");
}
}
让我们定义spring 配置类,其中我们将定义从 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
global-session
Spring Bean 请求和会话范围
对于 spring bean 请求和会话范围示例,我们将创建 Spring Boot Web 应用程序。创建一个 Spring Boot 启动项目并选择“Web”,以便我们可以将其作为 Web 应用程序运行。我们的最终项目将如下图所示。 ServletInitializer
并且SpringBootMvcApplication
是自动生成的 Spring Boot 类。我们不需要在那里做任何更改。这是我的 pom.xml 文件,看看我们应用程序的依赖项。您的 pom.xml 文件可能会根据您使用的 Eclipse 版本略有不同。
<?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
和session
。
Spring Bean 请求范围
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;
}
}
Spring Bean 会话范围
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 组件并使用 spring 自动配置上述 bean。
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 控制器
最后,让我们创建一个 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 会话超时配置
最后,我们必须配置 spring boot 会话超时变量,在中添加以下属性src/main/resources/application.properties
。
server.session.cookie.max-age= 1
server.session.timeout= 1
现在,具有会话范围的 Spring Bean 将在一分钟内失效。只需将该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()
Spring Bean 请求范围测试
打开任意浏览器并转到 URLhttps://localhost:8080/nameRS
并检查控制台输出。您应该看到DataRequestScope Constructor Called
每个请求都打印出来。
Spring Bean 会话范围测试
转到https://localhost:8080/nameSS
,您将获得以下输出。现在转到,https://localhost:8080/nameSSUpdated
以便将DataSessionScope
名称值更新为Session Scope Updated
。现在再次转到https://localhost:8080/nameSS
,您应该会看到更新的值。此时,您应该DataSessionScope Constructor Called at XXX
只会在控制台输出中看到一次。现在等待 1 分钟,以便我们的会话范围 bean 失效。然后再次转到,https://localhost:8080/nameSS
您应该会看到原始输出。您还应该检查控制台消息以了解容器是否再次创建了 DataSessionScope。这就是 spring beans 范围教程的全部内容。
您可以从我们的GitHub 存储库下载 Spring Beans Scope Spring Boot 项目。