Spring 5 中文解析核心篇 -IoC 容器之 Bean 作用域
当你创建一个bean
的定义时候,你可以创建一个模版(recipe)通过bean
定义的类定义去创建一个真实的实例。bean
定义是模版(recipe)的概念很重要,因为这意味着,与使用类一样,你可以从一个模版(recipe)创建多个对象实例。
你不仅可以控制要插入到从特定bean
定义创建的对象中的各种依赖项和配置值,还可以控制从特定bean
定义创建的对象的作用域。这种方法是非常有用的和灵活的,因为你可以选择通过配置创建的对象的作用域,而不必在Java类级别上考虑对象的作用域。bean
能够定义部署到一个或多个作用域。Spring
框架支撑6种作用域,4种仅仅使用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
类似下面例子:
1.5.2 原型作用域
非单例原型bean
的作用域部署结果是在每一次请求指定bean
的时候都会创建一个bean
实例。也就是,bean
被注入到其他bean
或在容器通过getBean()
方法调用都会创建一个新bean
。通常,为所有的无状态bean使用原型作用域并且有状态bean
使用单例bean
作用域。
下面的图说明Spring
的单例作用域:
数据访问对象(DAO
)通常不被配置作为一个原型,因为典型的DAO
不会维持任何会话状态。我们可以更容易地重用单例图的核心。
下面例子在XML
中定义一个原型bean
:
与其他作用域对比,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配置
Request
作用域Session作用域
Application作用域
作用域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注入到协作对象中时,你需要以下(正确和完整)配置,如以下示例所示:
代理类型选择
1.5.5 自定义作用域
bean
作用域机制是可扩展的。你可以定义你自己的作用域或者甚至重定义存在的作用域,尽管后者被认为是不好的做法,你不能覆盖内置的单例和原型范围。
创建一个自定义作用域
使用自定义作用域
作者
个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。
博客地址: http://youngitman.tech
CSDN: https://blog.csdn.net/liyong1028826685
微信公众号:
版权声明: 本文为 InfoQ 作者【青年IT男】的原创文章。
原文链接:【http://xie.infoq.cn/article/713350467282df18c2a48f55c】。
本文遵守【CC BY-NC】协议,转载请保留原文出处及本版权声明。
评论