Bean 作用域

创建 Bean 定义时,将创建用于创建实际实例的配方 由该 Bean 定义定义的类。bean 定义是 recipe 很重要,因为它意味着,与 class 一样,您可以创建多个对象 实例。spring-doc.cn

您不仅可以控制各种依赖项和配置值,这些依赖项和配置值 插入到从特定 bean 定义创建的对象中,但也插入到控件 从特定 Bean 定义创建的对象的范围。这种方法是 功能强大且灵活,因为您可以选择所创建对象的范围 通过配置,而不必在 Java 类级别。可以将 Bean 定义为部署在多个范围之一中。 Spring 框架支持六个范围,其中四个范围仅在 您使用 Web 感知 .您还可以创建自定义范围。ApplicationContextspring-doc.cn

下表描述了支持的范围:spring-doc.cn

表 1.Bean 作用域
范围 描述

单身 人士spring-doc.cn

(默认)将单个 bean 定义的范围限定为每个 Spring IoC 的单个对象实例 容器。spring-doc.cn

原型spring-doc.cn

将单个 Bean 定义的范围限定为任意数量的对象实例。spring-doc.cn

请求spring-doc.cn

将单个 Bean 定义的范围限定为单个 HTTP 请求的生命周期。那是 每个 HTTP 请求都有自己的 bean 实例,该实例是从单个 bean 的背面创建的 定义。仅在 web-aware Spring 的上下文中有效。ApplicationContextspring-doc.cn

会期spring-doc.cn

将单个 bean 定义的范围限定为 HTTP 的生命周期。仅在 Web 感知 Spring 的上下文。SessionApplicationContextspring-doc.cn

应用spring-doc.cn

将单个 Bean 定义的范围限定为 .仅在 Web 感知 Spring 的上下文。ServletContextApplicationContextspring-doc.cn

WebSocket 浏览器spring-doc.cn

将单个 Bean 定义的范围限定为 .仅在 Web 感知 Spring 的上下文。WebSocketApplicationContextspring-doc.cn

线程范围可用,但默认情况下未注册。有关更多信息, 请参阅 SimpleThreadScope 的文档。 有关如何注册此 Scope或任何其他自定义 Scope 的说明,请参阅 Using a Custom Scope

单例范围

仅管理单例 bean 的一个共享实例,以及 bean 的所有请求 替换为与该 Bean 定义的一个或多个 ID 匹配,则生成一个特定的 Bean 实例。spring-doc.cn

换句话说,当你定义一个 bean 定义并且它被定义为 singleton 时,Spring IoC 容器只创建对象的一个实例 由该 bean 定义定义。这个 single 实例存储在 singleton bean 以及该命名 bean 的所有后续请求和引用 返回缓存的对象。下图显示了单一实例范围的工作原理:spring-doc.cn

单身 人士

Spring 的单例 bean 概念不同于 Gang of Four (GoF) 模式书。GoF 单例对 Object 中,以便每个 ClassLoader 的Spring 单例的范围最好描述为每个容器 和每 bean 的 bean 中。这意味着,如果您在 单个 Spring 容器,则 Spring 容器将创建一个且仅创建一个实例 由该 Bean 定义定义的类。singleton scope 是默认范围 在Spring。要在 XML 中将 bean 定义为单例,可以定义一个 bean,如 以下示例:spring-doc.cn

<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 被注入到另一个 bean 中,或者您通过在 容器。通常,您应该对所有有状态 bean 使用 prototype 范围,并且 singleton 作用域。getBean()spring-doc.cn

下图说明了 Spring 原型范围:spring-doc.cn

原型

(数据访问对象 (DAO) 通常不配置为原型,因为典型的 DAO 不持有 任何对话状态。我们更容易重用 单例图。spring-doc.cn

下面的示例将 Bean 定义为 XML 中的原型:spring-doc.cn

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>

与其他范围相比, Spring 不管理 prototype bean 的 bean 中。容器实例化、配置和以其他方式组装 prototype 对象并将其交给客户端,不再记录该原型 实例。因此,尽管初始化生命周期回调方法在所有 对象,无论范围如何,在原型的情况下,配置销毁 生命周期回调。客户端代码必须清理 prototype-scoped 对象,并释放原型 bean 所持有的昂贵资源。要获取 Spring 容器来释放原型范围的 bean 持有的资源,请尝试使用 自定义 bean 后处理器,它保存对需要清理的 bean 的引用。spring-doc.cn

在某些方面, Spring 容器在原型范围的 bean 中的作用是 替换 Java 运算符。超过该点的所有生命周期管理都必须 由客户端处理。(有关 Spring 中 bean 生命周期的详细信息 容器,请参阅 生命周期回调newspring-doc.cn

具有原型 bean 依赖项的单例 bean

当您使用依赖于原型 bean 的单例作用域 bean 时,请注意 依赖项在实例化时解析。因此,如果你依赖注入一个 原型范围的 bean 转换为单例范围的 bean,则会实例化一个新的原型 bean 然后 dependency-injection 到单例 bean 中。prototype 实例是唯一的 实例。spring-doc.cn

但是,假设您希望单例范围的 bean 获取 prototype 范围的 bean。您不能依赖注入 prototype 作用域的 bean 添加到你的单例 bean 中,因为该注入只发生 一次,当 Spring 容器实例化单例 bean 并解析 并注入其依赖项。如果您需要原型 bean 的新实例,网址为 运行时,请参阅 Method Injectionspring-doc.cn

请求、会话、应用程序和 WebSocket 范围

、 、 和 范围 仅可用 如果使用 Web 感知的 Spring 实现(例如 )。如果你将这些作用域与常规的 Spring IoC 容器一起使用, 比如 , an 抱怨 关于未知的 bean 范围。requestsessionapplicationwebsocketApplicationContextXmlWebApplicationContextClassPathXmlApplicationContextIllegalStateExceptionspring-doc.cn

初始 Web 配置

为了支持在 、 和 级别(Web 范围的 bean)界定 bean 的范围,一些次要的初始配置是 在定义 bean 之前是必需的。(此初始设置不是必需的 对于标准范围:和 。requestsessionapplicationwebsocketsingletonprototypespring-doc.cn

如何完成此初始设置取决于特定的 Servlet 环境。spring-doc.cn

如果您在 Spring Web MVC 中访问范围限定的 bean,则实际上,在 由 Spring 处理,无需特殊设置。 already 公开所有相关状态。DispatcherServletDispatcherServletspring-doc.cn

如果使用 Servlet Web 容器,并且请求在 Spring 之外处理(例如,使用 JSF 时),则需要注册 . 这可以通过使用接口以编程方式完成。 或者,将以下声明添加到 Web 应用程序的文件中:DispatcherServletorg.springframework.web.context.request.RequestContextListenerServletRequestListenerWebApplicationInitializerweb.xmlspring-doc.cn

<web-app>
	...
	<listener>
		<listener-class>
			org.springframework.web.context.request.RequestContextListener
		</listener-class>
	</listener>
	...
</web-app>

或者,如果您的侦听器设置存在问题,请考虑使用 Spring 的 .过滤器映射取决于周围的 Web application configuration 的 app 配置,因此您必须根据需要对其进行更改。以下清单 显示了 Web 应用程序的 Filter 部分:RequestContextFilterspring-doc.cn

<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、 和 all 都完全执行 同样的事情,即将 HTTP 请求对象绑定到正在服务的 那个请求。这使得请求和会话范围的 bean 进一步可用 沿着调用链向下。RequestContextListenerRequestContextFilterThreadspring-doc.cn

请求范围

对于 Bean 定义,请考虑以下 XML 配置:spring-doc.cn

<bean id="loginAction" class="com.something.LoginAction" scope="request"/>

Spring 容器通过对每个 HTTP 请求使用 bean 定义来创建 bean 的新实例。也就是说,Bean 的范围限定在 HTTP 请求级别。您可以将内部的 state (状态),因为其他实例 从同一 bean 定义创建的 state 中看不到这些更改。 它们特定于单个请求。当请求完成处理时, 范围限定为请求的 bean 将被丢弃。LoginActionloginActionloginActionloginActionspring-doc.cn

当使用注解驱动的组件或 Java 配置时,注解 可用于将组件分配给范围。以下示例显示了如何操作 为此,请执行以下操作:@RequestScoperequestspring-doc.cn

@RequestScope
@Component
public class LoginAction {
	// ...
}
@RequestScope
@Component
class LoginAction {
	// ...
}

会话范围

对于 Bean 定义,请考虑以下 XML 配置:spring-doc.cn

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

Spring 容器在单个 HTTP 的生命周期中使用 bean 定义来创建 bean 的新实例。在其他 words,则 bean 的作用域实际上是在 HTTP 级别。如 使用请求范围的 bean,您可以更改实例的内部状态,即 根据需要创建尽可能多的 HTTP 实例,要知道其他 HTTP 实例也是 使用从同一 bean 定义创建的实例不会看到这些 状态更改,因为它们特定于单个 HTTP 。当 HTTP 最终被丢弃,范围限定为该特定 HTTP 的 bean 也被丢弃。UserPreferencesuserPreferencesSessionuserPreferencesSessionSessionuserPreferencesSessionSessionSessionspring-doc.cn

使用注释驱动的组件或 Java 配置时,可以使用注释将组件分配给范围。@SessionScopesessionspring-doc.cn

@SessionScope
@Component
public class UserPreferences {
	// ...
}
@SessionScope
@Component
class UserPreferences {
	// ...
}

应用范围

对于 Bean 定义,请考虑以下 XML 配置:spring-doc.cn

<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>

Spring 容器通过对整个 Web 应用程序使用一次 Bean 定义来创建 Bean 的新实例。也就是说,Bean 的范围限定在 level 上,并存储为 regular 属性。这有点类似于 Spring 单例 bean,但 在两个重要方面有所不同:它是 per , 而不是 per Spring 的单例(在任何给定的 Web 应用程序中都可能有多个), 它实际上是公开的,因此作为一个属性可见。AppPreferencesappPreferencesappPreferencesServletContextServletContextServletContextApplicationContextServletContextspring-doc.cn

使用注释驱动的组件或 Java 配置时,可以使用注释将组件分配给范围。这 以下示例显示了如何执行此操作:@ApplicationScopeapplicationspring-doc.cn

@ApplicationScope
@Component
public class AppPreferences {
	// ...
}
@ApplicationScope
@Component
class AppPreferences {
	// ...
}

WebSocket 范围

WebSocket 范围与 WebSocket 会话的生命周期相关联,并应用于 STOMP over WebSocket 应用程序,有关详细信息,请参阅 WebSocket 范围spring-doc.cn

作为依赖项的作用域 Bean

Spring IoC 容器不仅管理对象 (bean) 的实例化, 但也包括合作者(或依赖项)的联系。如果要注入 (对于 example) 将一个 HTTP 请求范围的 bean 转换为另一个寿命较长的 bean 中,您可以 选择注入 AOP 代理来代替作用域 Bean。也就是说,您需要注入 一个代理对象,它公开与 Scoped 对象相同的公共接口,但可以 还可以从相关范围(如 HTTP 请求)中检索真正的目标对象 并将方法调用委托到真实对象上。spring-doc.cn

您还可以在范围为 、 然后,引用通过可序列化的中间代理 因此能够在反序列化时重新获取目标 singleton bean。<aop:scoped-proxy/>singletonspring-doc.cn

当针对 scope 的 bean 进行声明时,每个方法 调用会导致创建一个新的目标实例,该实例的 然后,呼叫将被转发。<aop:scoped-proxy/>prototypespring-doc.cn

此外,作用域代理并不是从较短的作用域访问 bean 的唯一方法 生命周期安全时尚。您还可以声明注入点(即 constructor 或 setter 参数或 autowired 字段)设置为 , 允许调用以按需检索当前实例 时间 — 无需保留实例或单独存储实例。ObjectFactory<MyTargetBean>getObject()spring-doc.cn

作为扩展变体,您可以声明 which deliver 其他几种访问变体,包括 和 .ObjectProvider<MyTargetBean>getIfAvailablegetIfUniquespring-doc.cn

此的 JSR-330 变体被调用,并与声明和每次检索尝试的相应调用一起使用。 有关 JSR-330 的更多详细信息,请参阅此处ProviderProvider<MyTargetBean>get()spring-doc.cn

以下示例中的配置只有一行,但对于 了解其背后的 “为什么” 和 “如何”:spring-doc.cn

<?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 定义代理的行。

要创建这样的代理,您需要将子元素插入到 作用域 Bean 定义(请参见选择要创建的代理类型和基于 XML 架构的配置)。<aop:scoped-proxy/>spring-doc.cn

为什么 bean 的定义范围在 、 和 custom-scope 关卡需要元素吗? 考虑以下单例 bean 定义,并将其与 您需要为上述范围定义什么(请注意,以下 bean 定义并不完整):requestsession<aop:scoped-proxy/>userPreferencesspring-doc.cn

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

<bean id="userManager" class="com.something.UserManager">
	<property name="userPreferences" ref="userPreferences"/>
</bean>

在前面的示例中,单例 bean () 注入了一个引用 添加到 HTTP 范围的 Bean () 中。这里的要点是 bean 是一个单例:它每个 container 及其依赖项(在本例中只有一个 bean)是 也只注射了一次。这意味着 Bean 仅在 完全相同的对象(即最初注入的对象)。userManagerSessionuserPreferencesuserManageruserPreferencesuserManageruserPreferencesspring-doc.cn

当将生存期较短的作用域 bean 注入 生存期较长的作用域 bean(例如,注入 HTTP 作用域的协作 bean 作为单例 bean 的依赖项)。相反,您需要一个对象,并且在 HTTP 的生命周期中,您需要一个对象 特定于 HTTP .因此,容器会创建一个对象,该对象 公开与类完全相同的公共接口(理想情况下是 对象,它是一个实例),它可以从范围机制(HTTP 请求、 等 forth)。容器将此代理对象注入到 bean 中,即 不知道此引用是代理。在此示例中,当实例在依赖项注入的对象上调用方法时,它实际上是在代理上调用方法。然后,代理从 HTTP(在本例中)获取真实对象,并将 方法调用到检索到的 Real Object 上。SessionuserManagerSessionuserPreferencesSessionUserPreferencesUserPreferencesUserPreferencesSessionuserManagerUserPreferencesUserManagerUserPreferencesUserPreferencesSessionUserPreferencesspring-doc.cn

因此,在将 bean 注入协作对象时,您需要以下(正确和完整的)配置,如下例所示 显示:request-session-scopedspring-doc.cn

<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 容器为标记为 元素,将创建基于 CGLIB 的类代理。<aop:scoped-proxy/>spring-doc.cn

CGLIB 代理不会拦截私有方法。尝试调用私有方法 在这样的代理上,它不会委托给实际的 Scoped Target 对象。spring-doc.cn

或者,您可以将 Spring 容器配置为创建标准 JDK 基于接口的代理,通过为 元素的属性。使用 JDK 基于接口的代理意味着您的 application classpath 来影响此类代理。但是,这也意味着 作用域 Bean 必须至少实现一个接口,并且所有协作者 作用域 bean 注入到其中,必须通过其 接口。以下示例显示了基于接口的代理:falseproxy-target-class<aop:scoped-proxy/>spring-doc.cn

<!-- 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>

有关选择基于类或基于接口的代理的更多详细信息, 请参见代理机制spring-doc.cn

直接注入请求/会话引用

作为工厂范围的替代方案, Spring 还支持 将 、 、 和 (如果存在 JSF )注入并注入 Spring 管理的 bean,只需通过常规注入旁边的基于类型的自动装配即可 其他Beans的分数。Spring 通常会为这样的请求和会话注入代理 对象,它具有在单例 bean 和可序列化 bean 中工作的优势 此外,类似于工厂范围的 bean 的范围代理。WebApplicationContextHttpServletRequestHttpServletResponseHttpSessionWebRequestFacesContextExternalContextspring-doc.cn

自定义范围

Bean 范围机制是可扩展的。您可以定义自己的 范围,甚至重新定义现有范围,尽管后者被认为是不好的做法 并且你不能覆盖 built-in 和 scopes。singletonprototypespring-doc.cn

创建自定义范围

要将自定义范围集成到 Spring 容器中,您需要实现接口,如下所述 部分。有关如何实现自己的范围的想法,请参阅 Spring 框架本身提供的实现和 Scope javadoc。 其中更详细地解释了您需要实现的方法。org.springframework.beans.factory.config.ScopeScopespring-doc.cn

该接口有四种方法从 scope 中获取对象,从 范围,然后销毁它们。Scopespring-doc.cn

例如,会话范围的实现返回会话范围的 Bean(如果它 不存在,则该方法在将 Bean 绑定到 会话以供将来参考)。以下方法从 底层范围:spring-doc.cn

Object get(String name, ObjectFactory<?> objectFactory)
fun get(name: String, objectFactory: ObjectFactory<*>): Any

例如,会话范围实现从 基础会话。应该返回该对象,但如果 找不到具有指定名称的对象。以下方法从 底层范围:nullspring-doc.cn

Object remove(String name)
fun remove(name: String): Any

以下方法注册一个回调,范围应在 destroyed 或当 scope 中的指定对象被销毁时:spring-doc.cn

void registerDestructionCallback(String name, Runnable destructionCallback)
fun registerDestructionCallback(name: String, destructionCallback: Runnable)

有关销毁回调的更多信息,请参见 javadoc 或 Spring 范围实现。spring-doc.cn

以下方法获取基础范围的聊天标识符:spring-doc.cn

String getConversationId()
fun getConversationId(): String

此标识符对于每个范围都不同。对于会话范围的实现,此 identifier 可以是会话标识符。spring-doc.cn

使用自定义范围

在编写和测试一个或多个自定义实现后,您需要将 Spring 容器知道您的新范围。以下方法是中心 向 Spring 容器注册 new 的方法:ScopeScopespring-doc.cn

void registerScope(String scopeName, Scope scope);
fun registerScope(scopeName: String, scope: Scope)

此方法在接口上声明,该接口可用 通过 Spring 附带的大多数具体实现上的属性。ConfigurableBeanFactoryBeanFactoryApplicationContextspring-doc.cn

该方法的第一个参数是与 一个范围。Spring 容器本身中此类名称的示例是 和 。该方法的第二个参数是实际实例 。registerScope(..)singletonprototyperegisterScope(..)Scopespring-doc.cn

假设您编写了自定义实现,然后按所示注册它 在下一个示例中。Scopespring-doc.cn

下一个示例使用 Spring 中包含的 ,但不是 registered (默认)。对于您自己的自定义实现,说明是相同的。SimpleThreadScopeScope
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
val threadScope = SimpleThreadScope()
beanFactory.registerScope("thread", threadScope)

然后,您可以创建符合 custom 的 scopeping rules 的 bean 定义,如下所示:Scopespring-doc.cn

<bean id="..." class="..." scope="thread">

使用自定义实现,您不仅限于编程注册 的范围。您还可以使用类以声明方式进行注册,如下例所示:ScopeScopeCustomScopeConfigurerspring-doc.cn

<?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>
当你放置在实现的声明中时,确定范围的是工厂 Bean 本身,而不是对象 从 返回。<aop:scoped-proxy/><bean>FactoryBeangetObject()