Spring Bean范围
Spring Bean作用域允许我们更加精细地控制bean实例的创建。有时我们希望将bean实例创建为单例,但在其他情况下,我们可能希望在每个请求中创建它,或者在会话中创建一次。
Spring Bean 作用域
有五种类型的SpringBean作用域:
- 单例 – 在spring容器中只会创建一个spring bean实例。这是默认的spring bean范围。在使用这个范围时,确保bean没有共享实例变量,否则可能导致数据一致性问题。
原型 – 每次从spring容器中请求bean时都会创建一个新的实例。
请求 – 这与原型范围相同,但它适用于Web应用程序。每个HTTP请求都会为bean创建一个新实例。
会话 – 容器会为每个HTTP会话创建一个新的bean。
全局会话 – 用于为门户应用程序创建全局会话bean。
Spring Bean的单例(Singleton)和原型(Prototype)作用域
在独立的Spring应用程序中,可以使用单例和原型作用域的Spring Bean。让我们看看如何使用@Scope注解轻松配置这些作用域。假设我们有一个Java Bean类。
package com.Olivia.spring;
public class MyBean {
public MyBean() {
System.out.println("MyBean instance created");
}
}
让我们定义Spring配置类,在这里我们将定义从Spring容器获取 MyBean 实例的方法。
package com.Olivia.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();
}
}
请注意,单例是默认作用域,所以我们可以从上面的Bean定义中删除@Scope(value = “singleton”)。现在让我们创建一个主方法并测试单例作用域。
package com.Olivia.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实例。现在让我们将作用域更改为原型。
@Bean
@Scope(value="prototype")
public MyBean myBean() {
return new MyBean();
}
当执行主方法时,这次我们将会得到以下的输出结果。
MyBean instance created
867988177
MyBean instance created
443934570
很明显,每次从Spring容器请求时都会创建一个新的MyBean实例。现在让我们将作用域改为请求等级。
@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.Olivia.spring.MySpringApp.main(MySpringApp.java:12)
由于独立应用程序不支持请求、会话和全局会话范围,所以才会出现这个问题。
Spring Bean的请求和会话范围
<?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.Olivia.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>
我们来创建一些春天的组件,并将它们配置为在春天容器中作为请求和会话范围的春天bean。
Spring Bean的请求范围
package com.Olivia.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.Olivia.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.Olivia.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.Olivia.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.Olivia.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.Olivia.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.Olivia.spring.HelloData.helloSS()
SpringBean请求范围测试
在任何浏览器中打开,并输入网址https://localhost:8080/nameRS,然后查看控制台输出。您应该能看到在每个请求上都会打印出“DataRequestScope构造器已调用”。