1.5.Bean Scope
Bean在Spring 3.0之前存在6种作用域,3.0+ 存在7种作用域,多出的一种是线程作用域(Thread Scope)
- singleton:(默认)为每个Spring IoC容器将单个bean定义的作用域限定为单个对象实例。
- prototype:将单个bean定义的作用域限定为任意数量的对象实例。
- request:将单个bean定义的范围限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有一个在单个bean定义后面创建的bean实例。仅在可感知网络的Spring上下文中有效ApplicationContext。
- session:将单个bean定义的范围限定为HTTP的生命周期Session。仅在web的Spring上下文中有效ApplicationContext。
- application:将单个bean定义的作用域限定为的生命周期SerevletContext。仅在web的Spring上下文中有效ApplicationContext。
- websocket:将单个bean定义的作用域限定为的生命周期WebSocket。仅在web的Spring上下文中有效ApplicationContext。
1.5.1.Scope说明
Bean的Scope定义,就意味着Bean的生命周期的定义
singleton(单例)
Spring在默认情况下的Scope定义就是singleton;
单例模式下,Spring容器会在创建Bean时,调用缓存,是否存在被创建的Bean,如果存在,调用缓存中的Bean,则不需要进行创建;若不存在,对该Bean进行创建,并将其存储在容器缓存中,也就是这个Bean有且只会被创建一次
prototype(原型)
对prototype所定义的Bean发起请求时,Bean都会创建一个新的Bean实例;
建议:存在状态的Bean定义为prototype,无状态的Bean定义为singleton
- 与其他作用域相比,spring不能管理原型Bean的完整生命周期。
- 容器只是将原型对象实例化,配置或组装,然后将其交给客户端
- 用户必须通过客户端代码来清楚原型作用域内的对象,进而释放资源,释放资源可以使用bean后置处理器
- 从某种角度来看,原型模式,就是java中的new
注意:数据访问对象DAO一般不能配置为原型,因为正常的DAO不拥有任何对话状态
原型bean依赖单例bean存在的问题
默认情况下,单例bean中注入原型bean,单例bean每次调用的都是同一个原型bean;
但是如果需要单例bean每次都是重复获取原型bean的新实例,那么就要使用到方法注入
Request, Session, Application, and WebSocket 作用域
这几个作用域都是在Web环境下使用的,使用前要做一些初始配置
- 如果是在Spring Web MVC中访问这些模式的bean,实际上是由spring
DispatcherServlet处理的请求中访问,不需要进行其他设置,因为DispatcherServlet已经公开了所有的相关状态 - 如果使用的是Servlet 2.5的web容器,请求的进程就在spring DispatcherServlet外部,我们需要自行注册org.springframework.web.context.request.RequestContextListener,如果是Servlet 3.0+,可以通过实现WebApplicationInitializer接口来使用代码的方式来实现注册
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
如果监听器不能使用,我们可以使用Spring的RequestContextFilter过滤器
<web-app>
...
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
DispatcherServlet,RequestContextListener和RequestContextFilter都做完全相同的事情,就是将HTTP请求对象绑定到Thread为该请求提供服务的对象。这使得在请求链和会话范围内的Bean可以在调用链的更下游使用。
request作用域
在Spring容器中,在每一次HTTP请求时,都会创建不同的bean对象,所以我们可以对这些Bean对象进行随意的改变其中的状态,其他请求不会受到影响。它们只能作用于单个请求,当请求完成处理时,这个bean会被销毁
Session作用域
该Bean的生命周期,在每次会话中
Application作用域
被Application定义的Bean,表示这个Bean的作用域是在ServletContext,并存储为常规ServletContext属性;与单例模式相类似,但是有两个区别
- 它是每个ServletContext而不是每个Spring
ApplicationContext的单例(在一些特定的web应用程序中ServletContext可能是多个的) - 它是公开的,因此ServletContext可以看作一个属性
1.5.2.作用域小的Bean依赖作用域大的Bean问题
Spring IoC容器不仅管理Bean的实例化,而且还管理依赖注入之间的连接。
如果我们要将一个request范围的bean注入另一个作用域范围更广的bean中,那么我们可以选择注入AOP代理来代替request bean,这个代理会公开这个request bean的公共接口,我们可以从这个接口中,获取的真正的目标对象,也就是request bean
如何使用代理
使用标签:<aop:scoped-proxy/>
- 如果是在单例bean下使用,这个单例bean会经过可序列化,然后可以通过反序列化重新获得这个单例bean
- 如果是原型bean,那么代理上的每个方法每次调用都会导致创建新的目标实例,然后将调用转发到目标实例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- an HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/>
</bean>
<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userService" class="com.something.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
除了使用代理以外,我们还可以将注入点声明为ObjectFactory,然后通过getObject()来调用当前实例,不需要保存这个实例
1.5.3.自定义Scope
Bean的作用域是可扩展的,我们可以自定义作用域或者重新定义已有的作用域
创建自定义的scope
要在spring中创建自定义的scope,我们需要实现org.springframework.beans.factory.config.Scope接口,其中有四种方法,可以从作用域中获取对象,删除对象,销毁作用域或者其中对象,返回scope id
使用自定义的scope
在编写了自定义Scope的实现后,将其注册到spring容器中
void registerScope(String scopeName, Scope scope);
此方法在ConfigurableBeanFactory接口上声明,该接口可通过 Spring附带的BeanFactory大多数具体ApplicationContext实现上的属性获得。