在Java体系架构中Spring占有非常重要的一席之地,Spring不仅凭借着核心的控制反转(Ioc
)和依赖注入(DI
)改变了传统应用方式何时创建对象和实例化对象的反转流程赢得了广大Java开发者的喜欢,更为开箱即用的SpringBoot程序和Spring Cloud微服务等提供了框架。除了Spring的IoC和DI外,在Spring应用程序中,管理Bean的生命周期和作用域是至关重要的。Bean Scope定义了Bean实例的创建和销毁方式,决定了在应用程序中如何共享或隔离Bean。
什么是BeanScope
Spring中的BeanScope决定了容器在何时创建一个新的Bean实例以及何时重用现有实例。这对于控制应用程序中各个Bean的生命周期和状态非常重要。
Spring的Bean的作用域主要包括如下:
singleton(单例模式)
如果一个bean
的作用域为Singleton
,那么Spring IoC
容器中只会存在一个共享的bean
实例。当获取bean
时,只要id
与该bean
相匹配,就会返回同一bean
的实例。Singleton
是单例模式,就是在创建容器时就同时自动创建了一个bean
的对象,不管你是否使用,它都已经存在了,且每次获取到的对象都是同一个对象。
Singleton
作用域是Spring
中bean
的默认作用域。我们也可以在XML
中显示地将bean
定义成singleton
。如:
userBeans.xml
文件:
<bean id="user" class="com.atangbiji.pojo.User" scope="singleton">
<property name="name" value="阿汤"/>
<property name="age" value="18"/>
</bean>
附:测试。
MyTest.java
文件:
import com.atangbiji.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void testDI3(){
//userBeans.xml文件,IOC容器生成并管理相应的Bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml");
//通过Spring的上下文对象,从IOC容器中获取Bean对象
User user1 = (User)context.getBean("user");
User user2 = (User)context.getBean("user");
//输出两个对象的HashCode,并判断两个对象是否相同
System.out.println(user1.hashCode());
System.out.println(user2.hashCode());
System.out.println(user1 == user2);
}
}
prototype(原型模式)
若一个bean
的作用域为Prototype
,则表示一个bean
的定义可以对应多个对象实例。Prototype
作用域的bean
会导致在每次对该bean
请求(将其注入到另一个bean
中,或者以程序的方式调用容器的getBean()
方法)时都会创建一个新的bean
实例。
Prototype
是原型模式,它在我们创建容器的时候并没有实例化,而是当我们获取bean
的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。在XML
中将bean
定义成prototype
,可以这样配置:
userBeans.xml
文件:
<bean id="user" class="com.atangbiji.pojo.User" scope="prototype">
<property name="name" value="阿汤"/>
<property name="age" value="18"/>
</bean>
request 作用域
request 作用域:Bean 在一个 HTTP 请求内有效。当请求开始时,Spring 容器会为每个新的 HTTP 请求创建一个新的 Bean 实例,这个 Bean 在当前 HTTP 请求内是有效的,请求结束后,Bean 就会被销毁。如果在同一个请求中多次获取该 Bean,就会得到同一个实例,但是在不同的请求中获取的实例将会不同。
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopedBean {
// 在一次Http请求内共享的数据
private String requestData;
public void setRequestData(String requestData) {
this.requestData = requestData;
}
public String getRequestData() {
return this.requestData;
}
}
上述 Bean 在一个 HTTP 请求的生命周期内是一个单例,每个新的 HTTP 请求都会创建一个新的 Bean 实例。
session 作用域
session 作用域:Bean 是在同一个 HTTP 会话(Session)中是单例的。也就是说,从用户登录开始,到用户退出登录(或者 Session 超时)结束,这个过程中,不管用户进行了多少次 HTTP 请求,只要是在同一个会话中,都会使用同一个 Bean 实例。
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionScopedBean {
// 在一个Http会话内共享的数据
private String sessionData;
public void setSessionData(String sessionData) {
this.sessionData = sessionData;
}
public String getSessionData() {
return this.sessionData;
}
}
这样的设计对于存储和管理会话级别的数据非常有用,例如用户的登录信息、购物车信息等。因为它们是在同一个会话中保持一致的,所以使用 session 作用域的 Bean 可以很好地解决这个问题。
但是实际开发中没人这么干,会话 id 都会存在数据库,根据会话 id 就能在各种表中获取数据,避免频繁查库也是把关键信息序列化后存在 Redis。
application 作用域
application 作用域:在整个 Web 应用的生命周期内,Spring 容器只会创建一个 Bean 实例。这个 Bean 在 Web 应用的生命周期内都是有效的,当 Web 应用停止后,Bean 就会被销毁。
@Component
@Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ApplicationScopedBean {
// 在整个Web应用的生命周期内共享的数据
private String applicationData;
public void setApplicationData(String applicationData) {
this.applicationData = applicationData;
}
public String getApplicationData() {
return this.applicationData;
}
}
如果在一个 application 作用域的 Bean 上调用 setter 方法,那么这个变更将对所有用户和会话可见。后续对这个 Bean 的所有调用(包括 getter 和 setter)都将影响到同一个 Bean 实例,后面的调用会覆盖前面的状态。
websocket 作用域
websocket 作用域:Bean 在每一个新的 WebSocket 会话中都会被创建一次,就像 session 作用域的 Bean 在每一个 HTTP 会话中都会被创建一次一样。这个 Bean 在整个 WebSocket 会话内都是有效的,当 WebSocket 会话结束后,Bean 就会被销毁。
@Component
@Scope(value = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class WebSocketScopedBean {
// 在一个WebSocket会话内共享的数据
private String socketData;
public void setSocketData(String socketData) {
this.socketData = socketData;
}
public String getSocketData() {
return this.socketData;
}
}
上述 Bean 在一个 WebSocket 会话的生命周期内是一个单例,每个新的 WebSocket 会话都会创建一个新的 Bean 实例。
这个作用域需要 Spring Websocket 模块支持,并且应用需要配置为使用 websocket。
作用域设置
我们可以通过 @Scope 注解来设置 Bean 的作用域,它的设置方式有以下两种:
-
直接设置作用域的具体值,如:@Scope("prototype");
-
设置 ConfigurableBeanFactory 和 WebApplicationContext 提供的 SCOPE_XXX 变量,如 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)。
具体设置代码如下:
Bean 的作用域是指 Bean 在 Spring 整个框架中的某种行为模式。Bean 的常见作用域有 5 种:singleton(单例作用域)、prototype(原型作用域)、request(请求作用域)、session(请求作用域)、application(全局作用域),注意后 3 种作用域只适用于 Spring MVC 框架。