当你创建一个 bean bean bean
的定义时候,你可以创建一个模版(recipe)通过
定义的类定义去创建一个真实的实例。
定义是模版(recipe)的概念很重要,因为这意味着,与使用类一样,你可以从一个模版(recipe)创建多个对象实例。
你不仅可以控制要插入到从特定
bean
定义创建的对象中的各种依赖项和配置值,还可以控制从特定 bean
定义创建的对象的作用域。这种方法是非常有用的和灵活的,因为你可以选择通过配置创建的对象的作用域,而不必在Java类级别上考虑对象的作用域。 bean
能够定义部署到一个或多个作用域。 Spring
框架支撑6种作用域,4种仅仅使用 web
环境。你可以创建定制的作用域。
下面的表格描述了支撑的作用域:
Scope | Description |
---|---|
singleton | (默认)将每个Spring IoC容器的单个bean定义范围限定为单个对象实例。 |
prototype | 将单个bean定义的作用域限定为任意数量的对象实例 |
request | 将单个bean定义的范围限定为单个HTTP请求的生命周期。也就是,每个HTTP请拥有一个被创建的bean实例。仅在Spring ApplicationContext Web容器有效 |
session | 将单个bean定义的范围限制在HTTP Session生命周期。仅在Spring ApplicationContext Web容器有效 |
application | 将单个bean定义的范围限制在ServletContext生命周期。仅在Spring ApplicationContext Web容器有效 |
websocket | 将单个bean定义限制在WebSocket生命周期。仅在Spring ApplicationContext Web容器有效 |
从
Spring3.0
后,线程安全作用域是有效的但默认没有注册。更多的信息,查看文档
SimpleThreadScope
。更多关于怎样去注册和自定义作用域,查看
自定义作用域1.5.1 单例bean作用域
单例
bean
仅仅只有一个共享实例被容器管理,并且所有对具有与该
bean
定义相匹配的
ID
的
bean
的请求都会导致该特定
bean
实例被
Spring
容器返回。换一种方式,当你定义一个
bean
的定义并且它的作用域是单例的时候,
Spring IoC
容器创建通过
bean
定义的对象定义的实例。这个单例存储在缓存中,并且对命名
bean
的所有请求和引用返回的是缓存对象。下面图片展示了单例
bean
作用域是怎样工作的:

Spring
的单例
bean
概念与在
GoF
设计模式书中的单例模式不同。
GoF
单例硬编码对应的作用域例如:只有一个特定类的对象实例对每一个
ClassLoader
只创建一个对象实例。最好将
Spring
单例的范围描述为每个容器和每个
bean
(备注:
GoF
设计模式中的单例
bean
是针对不同
ClassLoader
来说的,而
Spring
的单例是针对不同容器级别的)。这意味着,如果在单个
Spring
容器对指定类定义一个
bean
,
Spring
容器通过
bean
定义的类创建一个实例。在
Spring
中单例作用域是默认的。在XML中去定义一个
bean
为单例,你可以定义一个
bean
类似下面例子:
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- 通过scope指定bean作用域 单例:singleton ,原型:prototype-->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
1.5.2 原型作用域
非单例原型
bean
的作用域部署结果是在每一次请求指定
bean
的时候都会创建一个
bean
实例。也就是,
bean
被注入到其他
bean
或在容器通过
getBean()
方法调用都会创建一个新
bean
。通常,为所有的无状态bean使用原型作用域并且有状态
bean
使用单例
bean
作用域。
下面的图说明
Spring
的单例作用域:
数据访问对象(
DAO
)通常不被配置作为一个原型,因为典型的
DAO
不会维持任何会话状态。我们可以更容易地重用单例图的核心。
下面例子在
XML
中定义一个原型
bean
:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
与其他作用域对比,
Spring
没有管理原型
bean
的完整生命周期。容器将实例化、配置或以其他方式组装原型对象,然后将其交给客户端,无需对该原型实例的进一步记录。因此,尽管初始化生命周期回调函数在所有对象上被回调而不管作用域如何,在原型情况下,配置销毁生命周期回调是不被回调。客户端代码必须清除原型作用域内的对象并释放原型
Bean
占用的昂贵资源。为了让
Spring
容器释放原型作用域
bean
所拥有的资源,请尝试使用自定义
bean
post-processor 后置处理器,该后处理器包含对需要清理的
bean
的引用(可以通过后置处理器释放引用资源)。
在某些方面,
Spring
容器在原型范围内的
bean
角色是
Java new
运算符的替代。所有超过该点的生命周期管理都必须由客户端处理。(更多关于在
Spring
容器中的
bean
生命周期,查看
生命周期回调)
1.5.3 单例bean与原型bean的依赖
当你使用依赖于原型
bean
的单例作用域
bean
时(单例引用原型
bean
),需要注意的是这些依赖项在初始化时候被解析。因此,如果你依赖注入一个原型
bean
到一个单例
bean
中,一个新原型
bean
被初始化并且依赖注入到一个单例
bean
。原型实例是唯一一个被提供给单例作用域
bean
的实例。(备注:单例引用原型bean时原型bean只会有一个)
然而,假设你希望单例作用域
bean
在运行时重复获取原型作用域
bean
的一个新实例。你不能依赖注入一个原型
bean
bean
,因为注入只发生一次,当
Spring
容器实例化单例
bean
、解析和注入它的依赖时。如果在运行时不止一次需要原型
bean
的新实例,查看
方法注入1.5.4 Request, Session, Application, and WebSocket Scopes
request
、
session
application
、和
websocket
作用域仅仅在你使用
Spring
ApplicationContext
实现(例如:
XmlWebApplicationContext
)时有效。如果你将这些作用域与常规的
Spring IoC
容器(例如
ClassPathXmlApplicationContext
)一起使用,则会抛出一个
IllegalStateException
异常,该错抛出未知的
bean
- 初始化Web配置
为了支持这些bean的作用域在
request
session
application
websocket
级别(web作用域bean)。一些次要的初始化配置在你定义你的bean之前是需要的。(这个初始化安装对于标准的作用域是不需要的:
singleton
prototype
)。
如何完成这个初始化安装依赖于你的特定
Servlet
环境。
如果在
Spring Web MVC
中访问作用域
bean
,实际上,在由
Spring
DispatcherServlet
处理的请求中,不需要特殊的设置。
DispatcherServlet
已经暴露了所有相关状态。
如果你使用
Servlet 2.5
Web
容器,请求处理在
Spring
DispatcherServlet
外(例如:当使用
JSF
或
Structs
),你需要去注册
org.springframework.web.context.request.RequestContextListener
ServletRequestListener
。对于
Servlet 3.0+
,这可以通过使用
WebApplicationInitializer
接口以编程方式完成。或者,对于旧的容器,增加下面声明到你的
web
应用程序
web.xml
文件:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
或者,如果你的监听器设置有问题,考虑使用
Spring
RequestContextFilter
。过滤器映射取决于周围的
Web
应用程序配置。因此你必须适当的改变它。下面的清单显示
web
filter
的部分配置:
<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
请求对象绑定到为该请求提供服务的线程。这使得
request
session
范围的bean在调用链的更下方可用。
-
作用域Request
考虑下面的XML关于
bean
的定义:
<!--请求作用域为request-->
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring
容器通过使用
LoginAction
bean定义为每个
HTTP
的请求创建一个
LoginAction
新实例bean。也就是说,
loginAction
bean的作用域在
HTTP
请求级别。你可以根据需要更改所创建实例的内部状态。因为从同一
loginAction
bean定义创建的其他实例看不到状态的这些变化。当这个请求处理完成,bean的作用域从
request
丢弃。(备注:
scope="request"
每个请求是线程级别隔离的、互不干扰)
当使用注解驱动组件或
Java Config
时,
@RequestScope
注解能够赋值一个组件到
request
作用域。下面的例子展示怎样使用:
@RequestScope//指定作用域访问为request
@Component
public class LoginAction {
// ...
}
- Session作用域
考虑下面为
bean
定义的
XML
配置:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring
userPreferences
的bean定义为单个
HTTP
Session
的生命周期内的创建一个
UserPreferences
的新实例。换句话说,
userPreferences
bean有效地作用在HTTP会话级别。与请求范围的Bean一样,您可以根据需要任意更改所创建实例的内部状态,因为知道其他也在使用从同一
`userPreferences
Bean定义创建的实例的HTTP Session实例也看不到这些状态变化,因为它们特定于单个HTTP会话。当
HTTP
会话最终被丢弃时,作用于该特定
HTTP
会话的
bean
也将被丢弃。
Java Config
@SessionScope
session
@SessionScope
@Component
public class UserPreferences {
// ...
}
- Application作用域
bean
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring
appPreferences
的bean定义为整个
Web
应用创建一个
AppPreferences
的bean新实例。也就是说,
appPreferences
的作用域在
ServletContext
级别并且作为一个常规的
ServletContext
属性被储存。这个和
Spring
的单例bean类似,但有两个重要的区别:每个
ServletContext
是一个单例,而不是每个
Spring
ApplicationContext
(在给定的
Web
应用程序中可能有多个),并且它实际上是暴露的,因此作为
ServletContext
属性可见。
Java Config
@ApplicationScope
application
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
- 作用域bean作为依赖项
Spring IoC容器不仅管理对象(bean)的实例化,而且还管理协同者(或依赖项)的连接。(例如)如果要将HTTP请求范围的Bean注入另一个作用域更长的Bean,则可以选择注入AOP代理来代替已定义范围的Bean。也就是说,你需要注入一个代理对象,该对象暴露与范围对象相同的公共接口,但也可以从相关范围(例如HTTP请求)中检索实际目标对象,并将方法调用委托给实际对象。
在这些作用域是单例之间,你可以使用
bean
。然后通过一个可序列化的中间代理引用,从而能够在反序列化时重新获得目标单例
<aop:scoped-proxy/>
bean
。
当申明
原型作用域
<aop:scoped-proxy/>
bean
,每个方法调用共享代理导致一个新目标实例被创建,然后将该调用转发到该目标实例。
同样,作用域代理不是以生命周期安全的方式从较短的作用域访问
的唯一方法。你也可以声明你的注入点(也就是,构造函数或者
bean
参数或自动注入字段)例如:
Setter
,允许
ObjectFactory<MyTargetBean>
getObject()
调用在每次需要时按需检索当前实例-不保留实例或单独存储实例。
作为一个扩展的变体,你可以声明
,提供了一些附加的获取方式,包括
ObjectProvider<MyTargetBean>
getIfAvailable
JSR-330的这种变体称为
getIfUnique
,并与
Provider
声明和每次检索尝试的相应
Provider <MyTargetBean>
调用一起使用。有关整体JSR-330的更多详细信息, 请参见此处
get()
在下面的例子中只需要一行配置,但是重要的是理解背后的原因:
<?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">
<!-- 一个HTTP Session作用域的bean暴露为一个代理 -->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<!-- 指示容器代理周围的bean -->
<aop:scoped-proxy/> //1.
</bean>
<!-- 一个单例作用域bean 被注入一个上面的代理bean -->
<bean id="userService" class="com.something.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
- 这行定义代理。
创建一个代理,通过插入一个子
<aop:scoped-proxy/>
元素到一个作用域
bean
定义中(查看
选择代理类型去创建 基于Schema的XML配置)。为什么这些
bean
的定义在
request
session
和自定义作用域需要
<aop:scoped-proxy/>
元素?考虑以下单例
bean
定义,并将其与需要为上述范围定义的内容进行对比(请注意,以下
userPreferences
bean定义不完整):
<!--没有<aop:scoped-proxy/> 元素-->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的例子中,单例bean (
userManager
) 被注入一个引用到
HTTP
Session
作用域的
bean
(
userPreferences
)。这个显著点是
userManager
bean是一个单例bean:这个实例在每个容器值初始化一次,并且它的依赖(在这个例子仅仅一个,
userPreferences
bean)仅仅被注入一次。这意味着
userManager
bean运行仅仅在相同的
userPreferences
对象上(也就是,最初注入的那个)。
当注入一个短生命周期作用域的
bean
到一个长生命周期作用域
bean
的时候这个不是我们期望的方式(例如:注入一个HTTP
Session
作用域的协同者
bean
作为一个依赖注入到单例
bean
)。相反,你只需要一个
userManager
对象,并且在
HTTP
会话的生存期内,你需要一个特定于HTTP会话的
userPreferences
对象。因此,容器创建一个对象,该对象公开与
UserPreferences
类完全相同的公共接口(理想地,对象是
UserPreferences
实例),可以从作用域机制(HTTP 请求,
Session
,以此类推)获取真正的
UserPreferences
对象。容器注入这个代理对象到
userManager
bean,这并不知道此
UserPreferences
引用是代理。在这个例子中,当
UserManager
实例调用在依赖注入
UserPreferences
对象上的方法时,它实际上是在代理上调用方法。然后代理从(在本例中)
HTTP
会话中获取实际的
UserPreferences
对象,并将方法调用委托给检索到的实际
UserPreferences
对象。
因此,在将
request-scoped
session-scoped
的bean注入到协作对象中时,你需要以下(正确和完整)配置,如以下示例所示:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
- 代理类型选择
默认情况下,当
Spring
容器为
bean
创建一个代理,这个
bean
通过
<aop:scoped-proxy/>
元素被标记,基于
CGLIB
的类代理被创建。
CGLIB
代理拦截器仅仅公共方法被调用!在代理上不要调用非公共方法。
或者,你可以为作用域
bean
配置
Spring
容器创建标准的
JDK
基于接口的代理,通过指定
<aop:scoped-proxy/>
元素的
proxy-target-class
属性值为
false
。使用基于JDK接口的代理意味着你不需要应用程序类路径中的其他库即可影响此类代理(备注:意思是没有额外的依赖)。但是,这也意味着作用域
Bean
的类必须实现至少一个接口,并且作用域
Bean
注入到其中的所有协同者必须通过其接口之一引用该
Bean
。以下示例显示基于接口的代理:
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<!--基于接口代理-->
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.stuff.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
更多详细信息关于选择基于
class
或基于接口代理,参考
代理机制 参考代码: com.liyong.ioccontainer.starter.XmlBeanScopeIocContainer
1.5.5 自定义作用域
bean
作用域机制是可扩展的。你可以定义你自己的作用域或者甚至重定义存在的作用域,尽管后者被认为是不好的做法,你不能覆盖内置的单例和原型范围。
- 创建一个自定义作用域
去集成你的自定义作用域到
Spring
容器中,你需要去实现
org.springframework.beans.factory.config.Scope
接口,在这章中描述。有关如何实现自己的作用域的想法,查看
Scope
实现提供关于
Spring
框架自身和
Scope
的文档,其中详细说明了你需要实现的方法。
Scope
接口有四个方法从作用域获取对象,从作用域移除它们,并且让它们销毁。
例如:
Sesson
scope
实现返回
Season
bean
(如果它不存在,这个方法返回一个新的
bean
实例,将其绑定到会话以供将来引用)。 下面的方法从底层作用域返回对象:
Object get(String name, ObjectFactory<?> objectFactory)
Session
scope
实现移除Season作用域
bean
从底层的
Session
。对象应该被返回,但是如果这个对象指定的名称不存在你也可以返回
null
。下面的方法从底层作用域移除对象:
Object remove(String name)
以下方法注册在销毁作用域或销毁作用域中的指定对象时应执行的回调:
void registerDestructionCallback(String name, Runnable destructionCallback)
查看
javadoc或者
Spring
作用域实现关于更多销毁回调的信息。
以下方法获取基础作用域的会话标识符:
String getConversationId()
这个表示每个作用域是不同的。对于
Session
作用域的实现,此标识符可以是
Session
标识符。
- 使用自定义作用域
在你写并且测试一个或更多自定义
Scope
实现,你需要去让
Spring
容器知道你的新作用域。以下方法是在
Spring
容器中注册新范围的主要方法:
void registerScope(String scopeName, Scope scope);
这个方法在
ConfigurableBeanFactory
接口上被定义,该接口可通过
Spring
附带的大多数具体
ApplicationContext
实现上的
BeanFactory
属性获得。
registerScope(..)
方法第一个参数是唯一的名字关于作用域。
Spring
容器本身中的此类名称示例包括单例和原型。
registerScope(..)
方法第二个参数是自定义
Scope
实现
假设你写你的自定义
Scope
实现并且像下面的例子注册它。
接下来例子使用,它包括
SimpleThreadScope
但是默认是不被注册的。对于你自己的自定义范围实现,是相同的。
Spring
Scope threadScope = new SimpleThreadScope();
//注册自定义作用域
beanFactory.registerScope("thread", threadScope);
然后,你可以按照你的自定义范围的作用域规则创建bean定义,如下所示:
<bean id="..." class="..." scope="thread">
通过自定义
Scope
实现,你不仅限于以编程方式注册作用域。你可以声明式注册Scope,通过使用
CustomScopeConfigurer
,类似下面的例子:
<?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">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="thing2" class="x.y.Thing2" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="thing1" class="x.y.Thing1">
<property name="thing2" ref="thing2"/>
</bean>
</beans>
当在实现中配置
FactoryBean
时,限定作用域的是工厂
<aop:scoped-proxy/>
本身,而不是从
bean
返回对象。
getObject()
com.liyong.ioccontainer.starter.XmlCustomScopeIocContainer
作者
个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。
博客地址:
http://youngitman.techCSDN:
https://blog.csdn.net/liyong1028826685微信公众号:
技术交流群: