此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
Bean 作用域
创建 Bean 定义时,您将创建一个配方,用于创建该 Bean 定义定义的类的实际实例的。Bean 定义是recipe 的想法很重要,因为这意味着,与类一样,您可以从单个配方创建许多对象实例。
您不仅可以控制各种依赖项和配置值,这些依赖项和配置值将插入到从特定 Bean 定义创建的对象中,还可以控制从特定 Bean 定义创建的对象的范围。这种方法是强大且灵活,因为您可以选择您创建的对象的范围通过配置,而不必在 Java 的对象范围内烘焙类级别。可以将 Bean 定义为部署在多个范围之一中。Spring Framework 支持六个范围,其中四个仅在以下情况下可用您使用 Web 感知ApplicationContext
. 您还可以创建自定义范围。
支持的范围如下表所示:
范围 | 描述 |
---|---|
(默认)将单个 bean 定义的范围限定为每个 Spring IoC 的单个对象实例 容器。 |
|
将单个 Bean 定义的范围限定为任意数量的对象实例。 |
|
将单个 Bean 定义的范围限定为单个 HTTP 请求的生命周期。那是
每个 HTTP 请求都有自己的 Bean 实例,这些实例是在单个 Bean 的后面创建的
定义。仅在 Web 感知 Spring 的上下文中有效 |
|
将单个 Bean 定义的范围限定为 HTTP 的生命周期 |
|
将单个 Bean 定义的范围限定为 |
|
将单个 Bean 定义的范围限定为 |
线程作用域可用,但默认情况下未注册。欲了解更多信息,
请参阅文档SimpleThreadScope .
有关如何注册此或任何其他自定义范围的说明,请参阅使用自定义范围。 |
单例示波器
仅管理单例 Bean 的一个共享实例,并且对 Bean 的所有请求 与该 Bean 定义匹配的一个或多个 ID 会导致该特定 Bean 实例。
换句话说,当您定义一个 Bean 定义并且它的作用域为 singleton,则 Spring IoC 容器只创建对象的一个实例 由该 bean 定义定义。这个单个实例存储在这样的缓存中 单例 Bean,以及该命名 Bean 的所有后续请求和引用 返回缓存对象。下图显示了单例作用域的工作原理:

Spring 的单例 Bean 概念与 四人帮 (GoF) 模式书。GoF 单例对 对象,以便每个类只创建一个特定类的实例 ClassLoader 的 ClassLoader 中。Spring 单例的范围最好描述为每个容器 和每颗豆子。这意味着,如果您在 单个 Spring 容器,Spring 容器创建一个且仅一个实例 由该 bean 定义定义的类。单例作用域是默认作用域 在Spring。要在 XML 中将 Bean 定义为单例,您可以定义一个 Bean,如 以下示例:
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
原型范围
Bean 部署的非单例原型范围会导致创建新的
bean 实例。也就是说,豆子
被注入到另一个 Bean 中,或者您通过getBean()
方法调用
容器。通常,您应该对所有有状态 bean 使用原型作用域,并且
无状态 bean 的单例作用域。
下图说明了 Spring 原型作用域:

(数据访问对象 (DAO) 通常不配置为原型,因为典型的 DAO 不包含 任何对话状态。我们更容易重用 单例图。
以下示例将 bean 定义为 XML 中的原型:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
与其他作用域相比,Spring 不管理 原型豆。容器实例化、配置和以其他方式组装 prototype 对象并将其交给客户端,没有该原型的进一步记录 实例。因此,尽管初始化生命周期回调方法在所有 对象,无论范围如何,在原型的情况下,配置销毁 不会调用生命周期回调。客户端代码必须清理原型范围的 对象并释放原型 bean 持有的昂贵资源。获取 Spring 容器来释放原型范围的 bean 所持有的资源,请尝试使用 自定义 bean 后处理器,它包含对需要清理的 bean 的引用。
在某些方面,Spring 容器在原型范围的 bean 中的作用是
替换 Javanew
算子。超过该点的所有生命周期管理都必须
由客户处理。(有关 Spring 中 Bean 生命周期的详细信息
容器,请参阅生命周期回调。
具有原型 bean 依赖项的单例 Bean
当您使用依赖于原型 Bean 的单例范围 Bean 时,请注意 依赖关系在实例化时解析。因此,如果您依赖注入一个 原型作用域的 Bean 转换为单例作用域的 Bean,则实例化新的原型 Bean 然后依赖注入到单例 Bean 中。原型实例是唯一的 实例,该实例曾经提供给单例作用域的 bean。
但是,假设您希望单例作用域的 bean 获取 原型作用域的 bean 在运行时重复。您不能依赖注入 原型作用域的 bean 到您的单例 bean 中,因为该注入仅发生 一次,当 Spring 容器实例化单例 Bean 并解析 并注入其依赖项。如果您需要一个新的原型 Bean 实例,请位于 运行时,请参阅方法注入。
请求、会话、应用程序和 WebSocket 范围
这request
,session
,application
和websocket
范围仅可用
如果您使用 Web 感知的 SpringApplicationContext
实现(例如XmlWebApplicationContext
).如果将这些作用域与常规 Spring IoC 容器一起使用,
例如ClassPathXmlApplicationContext
一IllegalStateException
抱怨
抛出一个未知的 bean 作用域。
初始 Web 配置
支持在request
,session
,application
和websocket
级别(Web 范围的 bean),一些次要的初始配置是
required 在定义 bean 之前。(不需要此初始设置
对于标准示波器:singleton
和prototype
.)
如何完成此初始设置取决于您的特定 Servlet 环境。
如果您在 Spring Web MVC 中访问作用域 bean,则实际上,在请求中
由弹簧加工DispatcherServlet
,无需特殊设置。DispatcherServlet
已经暴露了所有相关状态。
如果您使用 Servlet Web 容器,则在 Spring 的DispatcherServlet
(例如,在使用 JSF 时),您需要注册org.springframework.web.context.request.RequestContextListener
ServletRequestListener
.
这可以通过使用WebApplicationInitializer
接口。
或者,将以下声明添加到 Web 应用程序的web.xml
文件:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
或者,如果您的侦听器设置有问题,请考虑使用 Spring 的RequestContextFilter
.过滤器映射取决于周围的网络
应用程序配置,因此您必须根据需要更改它。以下列表
显示 Web 应用程序的过滤器部分:
<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 进一步可用
呼叫链的下游。
请求范围
考虑 Bean 定义的以下 XML 配置:
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring 容器创建了一个LoginAction
bean 通过使用loginAction
每个 HTTP 请求的 bean 定义。也就是说,loginAction
bean 的范围为 HTTP 请求级别。您可以更改内部
根据需要创建的实例的状态,因为其他实例
从相同的loginAction
bean 定义在状态中看不到这些变化。
它们特定于个人要求。当请求完成处理时,
范围限定为请求的 bean 将被丢弃。
当使用注释驱动的组件或 Java 配置时,@RequestScope
注解
可用于将组件分配给request
范围。以下示例演示了如何
为此:
-
Java
-
Kotlin
@RequestScope
@Component
public class LoginAction {
// ...
}
@RequestScope
@Component
class LoginAction {
// ...
}
会话范围
考虑 Bean 定义的以下 XML 配置:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring 容器创建了一个UserPreferences
bean 通过使用userPreferences
单个 HTTP 生命周期的 bean 定义Session
.在其他
单词,userPreferences
bean 的有效作用域为 HTTPSession
水平。如
使用请求范围的 Bean,您可以更改实例的内部状态,即
随心所欲地创建,知道其他 HTTPSession
实例也是
使用从同一userPreferences
bean 定义看不到这些
状态更改,因为它们特定于单个 HTTPSession
.当
HTTPSession
最终被丢弃,则作用域限定为该特定 HTTP 的 BeanSession
也被丢弃了。
使用注释驱动的组件或 Java 配置时,可以使用@SessionScope
注释以将组件分配给session
范围。
-
Java
-
Kotlin
@SessionScope
@Component
public class UserPreferences {
// ...
}
@SessionScope
@Component
class UserPreferences {
// ...
}
应用范围
考虑 Bean 定义的以下 XML 配置:
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring 容器创建了一个AppPreferences
bean 通过使用appPreferences
bean 定义一次。也就是说,appPreferences
bean 的作用域为ServletContext
级别并存储为常规ServletContext
属性。这有点类似于 Spring 单例 bean,但是
在两个重要方面有所不同: 它是单例 perServletContext
,而不是每个弹簧ApplicationContext
(在任何给定的 Web 应用程序中可能有多个),
它实际上是公开的,因此可见为ServletContext
属性。
使用注释驱动的组件或 Java 配置时,可以使用@ApplicationScope
注释以将组件分配给application
范围。 这 以下示例显示了如何执行此作:
-
Java
-
Kotlin
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
@ApplicationScope
@Component
class AppPreferences {
// ...
}
WebSocket 作用域
WebSocket 作用域与 WebSocket 会话的生命周期相关联,适用于STOMP over WebSocket 应用程序,请参阅 WebSocket 作用域了解更多详细信息。
作用域 Bean 作为依赖项
Spring IoC 容器不仅管理对象(bean)的实例化,还管理协作者(或依赖项)的连接。如果您想注入(对于例如)HTTP 请求范围的 bean 到另一个寿命较长的 bean 中,您可以选择注入 AOP 代理来代替作用域 bean。也就是说,您需要注入一个代理对象,它公开与作用域对象相同的公共接口,但可以还可以从相关范围(例如 HTTP 请求)中检索真实目标对象并将方法调用委托给真实对象。
您还可以使用 声明时 此外,作用域代理并不是以生命周期安全方式从较短作用域访问 Bean 的唯一方法。您也可以将注入点(即构造函数或 setter 参数或自动连接字段)声明为 作为扩展变体,您可以声明 其 JSR-330 变体称为 |
以下示例中的配置只有一行,但重要的是 了解背后的“为什么”和“如何”:
<?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/> (1)
</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>
1 | 定义代理的行。 |
要创建此类代理,请插入一个子代理<aop:scoped-proxy/>
元素转换为
作用域 bean 定义(请参阅选择要创建的代理类型和基于 XML 模式的配置)。
为什么 bean 的定义作用域为request
,session
和 custom-scope级别需要<aop:scoped-proxy/>
常见场景中的元素?考虑以下单例 bean 定义,并将其与您需要为上述范围定义的内容(请注意,以下内容userPreferences
bean 定义目前是不完整的):
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的示例中,单例 Bean (userManager
) 注入引用到 HTTPSession
-作用域 bean (userPreferences
). 这里的突出点是userManager
bean 是一个单例:它每容器及其依赖项(在本例中只有一个,即userPreferences
bean)是也只注入一次。这意味着userManager
bean 仅在完全相同userPreferences
对象(即最初注入它的对象)。
这不是将寿命较短的作用域 Bean 注入寿命较长的作用域 Bean(例如,注入 HTTPSession
-范围协作bean 作为依赖项添加到单例 bean 中)。相反,您需要一个userManager
对象,并且,在 HTTP 的生命周期内Session
,您需要一个userPreferences
对象 特定于 HTTPSession
. 因此,容器创建一个对象,该对象公开与UserPreferences
类(理想情况下是对象,该对象是UserPreferences
instance),它可以获取实数UserPreferences
对象(HTTP 请求、Session
,依此类推forth)。容器将此代理对象注入到userManager
bean,这是不知道这个UserPreferences
reference 是一个代理。在此示例中,当UserManager
实例调用依赖注入的方法UserPreferences
对象,它实际上是在调用代理上的方法。然后代理获取实数UserPreferences
对象来自(在本例中)HTTPSession
并将method 调用委托到检索到的实数上UserPreferences
对象。
因此,在注入时需要以下(正确且完整的)配置request-
和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 容器为标记为
这<aop:scoped-proxy/>
元素,则创建基于 CGLIB 的类代理。
CGLIB 代理不会拦截私有方法。尝试调用私有方法 在这样的代理上,不会委托给实际作用域的目标对象。 |
或者,您可以配置 Spring 容器以创建标准 JDK
此类作用域 Bean 的基于接口的代理,通过指定false
对于
这proxy-target-class
属性的<aop:scoped-proxy/>
元素。 使用 JDK基于接口的代理意味着您不需要在应用程序类路径中添加额外的库来影响此类代理。但是,这也意味着作用域 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>
有关选择基于类或基于接口的代理的更多详细信息, 请参阅代理机制。
自定义范围
bean 作用域机制是可扩展的。您可以定义自己的
范围,甚至重新定义现有范围,尽管后者被认为是不好的做法
并且您不能覆盖内置的singleton
和prototype
范围。
创建自定义作用域
要将自定义范围集成到 Spring 容器中,您需要实现org.springframework.beans.factory.config.Scope
接口,在此
部分。有关如何实现您自己的范围的想法,请参阅Scope
Spring Framework 本身提供的实现和Scope
javadoc,
它更详细地解释了您需要实现的方法。
这Scope
接口有四种方法从作用域中获取对象,从
范围,让它们被摧毁。
例如,会话作用域实现返回会话作用域的 bean(如果它 不存在,则该方法在将 bean 绑定到 会议以备将来参考)。以下方法从 基础范围:
-
Java
-
Kotlin
Object get(String name, ObjectFactory<?> objectFactory)
fun get(name: String, objectFactory: ObjectFactory<*>): Any
例如,会话作用域实现从底层会话中删除会话作用域的 bean。应该返回该对象,但您可以返回null
如果找不到具有指定名称的对象。以下方法从底层作用域中删除对象:
-
Java
-
Kotlin
Object remove(String name)
fun remove(name: String): Any
以下方法注册一个回调,作用域应调用该回调destroyed 或当作用域中的指定对象被销毁时:
-
Java
-
Kotlin
void registerDestructionCallback(String name, Runnable destructionCallback)
fun registerDestructionCallback(name: String, destructionCallback: Runnable)
有关销毁回调的更多信息,请参阅 javadoc 或 Spring 作用域实现。
以下方法获取基础作用域的对话标识符:
-
Java
-
Kotlin
String getConversationId()
fun getConversationId(): String
每个作用域的此标识符都不同。对于会话范围的实现,此 identifier 可以是会话标识符。
使用自定义作用域
编写并测试一个或多个自定义Scope
实现,您需要使
Spring 容器知道你的新作用域。以下方法是中心
注册新Scope
使用 Spring 容器:
-
Java
-
Kotlin
void registerScope(String scopeName, Scope scope);
fun registerScope(scopeName: String, scope: Scope)
此方法在ConfigurableBeanFactory
接口,该接口可用
通过BeanFactory
大部分混凝土的属性ApplicationContext
Spring 附带的实现。
第一个参数registerScope(..)
method 是与
一个范围。Spring 容器本身中此类名称的示例是singleton
和prototype
.第二个参数registerScope(..)
method 是一个实际实例
的Scope
您希望注册和使用的实现。
假设你编写了自定义Scope
实现,然后如图所示注册
在下一个示例中。
下一个示例使用SimpleThreadScope ,它包含在 Spring 中,但不是
默认注册。对于您自己的自定义,说明将相同Scope 实现。 |
-
Java
-
Kotlin
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
val threadScope = SimpleThreadScope()
beanFactory.registerScope("thread", threadScope)
然后,您可以创建符合自定义范围规则的 Bean 定义Scope
如下:
<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>
当您放置<aop:scoped-proxy/> 在<bean> 声明FactoryBean 实现时,作用域的是工厂 bean 本身,而不是对象
返回自getObject() . |