此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10spring-doc.cadn.net.cn

Bean 作用域

创建 Bean 定义时,您将创建一个配方,用于创建该 Bean 定义定义的类的实际实例的。Bean 定义是recipe 的想法很重要,因为这意味着,与类一样,您可以从单个配方创建许多对象实例。spring-doc.cadn.net.cn

您不仅可以控制各种依赖项和配置值,这些依赖项和配置值将插入到从特定 Bean 定义创建的对象中,还可以控制从特定 Bean 定义创建的对象的范围。这种方法是强大且灵活,因为您可以选择您创建的对象的范围通过配置,而不必在 Java 的对象范围内烘焙类级别。可以将 Bean 定义为部署在多个范围之一中。Spring Framework 支持六个范围,其中四个仅在以下情况下可用您使用 Web 感知ApplicationContext. 您还可以创建自定义范围。spring-doc.cadn.net.cn

支持的范围如下表所示:spring-doc.cadn.net.cn

表 1.Bean 作用域
范围 描述

单身 人士spring-doc.cadn.net.cn

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

原型spring-doc.cadn.net.cn

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

请求spring-doc.cadn.net.cn

将单个 Bean 定义的范围限定为单个 HTTP 请求的生命周期。那是 每个 HTTP 请求都有自己的 Bean 实例,这些实例是在单个 Bean 的后面创建的 定义。仅在 Web 感知 Spring 的上下文中有效ApplicationContext.spring-doc.cadn.net.cn

会期spring-doc.cadn.net.cn

将单个 Bean 定义的范围限定为 HTTP 的生命周期Session.仅在以下地区有效 Web 感知 Spring 的上下文ApplicationContext.spring-doc.cadn.net.cn

应用spring-doc.cadn.net.cn

将单个 Bean 定义的范围限定为ServletContext.仅在以下地区有效 Web 感知 Spring 的上下文ApplicationContext.spring-doc.cadn.net.cn

网络套接字spring-doc.cadn.net.cn

将单个 Bean 定义的范围限定为WebSocket.仅在以下地区有效 Web 感知 Spring 的上下文ApplicationContext.spring-doc.cadn.net.cn

线程作用域可用,但默认情况下未注册。欲了解更多信息, 请参阅文档SimpleThreadScope. 有关如何注册此或任何其他自定义范围的说明,请参阅使用自定义范围

单例示波器

仅管理单例 Bean 的一个共享实例,并且对 Bean 的所有请求 与该 Bean 定义匹配的一个或多个 ID 会导致该特定 Bean 实例。spring-doc.cadn.net.cn

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

单身 人士

Spring 的单例 Bean 概念与 四人帮 (GoF) 模式书。GoF 单例对 对象,以便每个类只创建一个特定类的实例 ClassLoader 的 ClassLoader 中。Spring 单例的范围最好描述为每个容器 和每颗豆子。这意味着,如果您在 单个 Spring 容器,Spring 容器创建一个且仅一个实例 由该 bean 定义定义的类。单例作用域是默认作用域 在Spring。要在 XML 中将 Bean 定义为单例,您可以定义一个 Bean,如 以下示例:spring-doc.cadn.net.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 中,或者您通过getBean()方法调用 容器。通常,您应该对所有有状态 bean 使用原型作用域,并且 无状态 bean 的单例作用域。spring-doc.cadn.net.cn

下图说明了 Spring 原型作用域:spring-doc.cadn.net.cn

原型

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

以下示例将 bean 定义为 XML 中的原型:spring-doc.cadn.net.cn

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

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

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

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

当您使用依赖于原型 Bean 的单例范围 Bean 时,请注意 依赖关系在实例化时解析。因此,如果您依赖注入一个 原型作用域的 Bean 转换为单例作用域的 Bean,则实例化新的原型 Bean 然后依赖注入到单例 Bean 中。原型实例是唯一的 实例,该实例曾经提供给单例作用域的 bean。spring-doc.cadn.net.cn

但是,假设您希望单例作用域的 bean 获取 原型作用域的 bean 在运行时重复。您不能依赖注入 原型作用域的 bean 到您的单例 bean 中,因为该注入仅发生 一次,当 Spring 容器实例化单例 Bean 并解析 并注入其依赖项。如果您需要一个新的原型 Bean 实例,请位于 运行时,请参阅方法注入spring-doc.cadn.net.cn

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

request,session,applicationwebsocket范围仅可用 如果您使用 Web 感知的 SpringApplicationContext实现(例如XmlWebApplicationContext).如果将这些作用域与常规 Spring IoC 容器一起使用, 例如ClassPathXmlApplicationContextIllegalStateException抱怨 抛出一个未知的 bean 作用域。spring-doc.cadn.net.cn

初始 Web 配置

支持在request,session,applicationwebsocket级别(Web 范围的 bean),一些次要的初始配置是 required 在定义 bean 之前。(不需要此初始设置 对于标准示波器:singletonprototype.)spring-doc.cadn.net.cn

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

如果您在 Spring Web MVC 中访问作用域 bean,则实际上,在请求中 由弹簧加工DispatcherServlet,无需特殊设置。DispatcherServlet已经暴露了所有相关状态。spring-doc.cadn.net.cn

如果您使用 Servlet Web 容器,则在 Spring 的DispatcherServlet(例如,在使用 JSF 时),您需要注册org.springframework.web.context.request.RequestContextListener ServletRequestListener. 这可以通过使用WebApplicationInitializer接口。 或者,将以下声明添加到 Web 应用程序的web.xml文件:spring-doc.cadn.net.cn

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

或者,如果您的侦听器设置有问题,请考虑使用 Spring 的RequestContextFilter.过滤器映射取决于周围的网络 应用程序配置,因此您必须根据需要更改它。以下列表 显示 Web 应用程序的过滤器部分:spring-doc.cadn.net.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,RequestContextListenerRequestContextFilter都做到了 同样的事情,即将 HTTP 请求对象绑定到Thread那就是服务 那个请求。这使得请求和会话范围的 Bean 进一步可用 呼叫链的下游。spring-doc.cadn.net.cn

请求范围

考虑 Bean 定义的以下 XML 配置:spring-doc.cadn.net.cn

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

Spring 容器创建了一个LoginActionbean 通过使用loginAction每个 HTTP 请求的 bean 定义。也就是说,loginActionbean 的范围为 HTTP 请求级别。您可以更改内部 根据需要创建的实例的状态,因为其他实例 从相同的loginActionbean 定义在状态中看不到这些变化。 它们特定于个人要求。当请求完成处理时, 范围限定为请求的 bean 将被丢弃。spring-doc.cadn.net.cn

当使用注释驱动的组件或 Java 配置时,@RequestScope注解 可用于将组件分配给request范围。以下示例演示了如何 为此:spring-doc.cadn.net.cn

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

会话范围

考虑 Bean 定义的以下 XML 配置:spring-doc.cadn.net.cn

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

Spring 容器创建了一个UserPreferencesbean 通过使用userPreferences单个 HTTP 生命周期的 bean 定义Session.在其他 单词,userPreferencesbean 的有效作用域为 HTTPSession水平。如 使用请求范围的 Bean,您可以更改实例的内部状态,即 随心所欲地创建,知道其他 HTTPSession实例也是 使用从同一userPreferencesbean 定义看不到这些 状态更改,因为它们特定于单个 HTTPSession.当 HTTPSession最终被丢弃,则作用域限定为该特定 HTTP 的 BeanSession也被丢弃了。spring-doc.cadn.net.cn

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

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

应用范围

考虑 Bean 定义的以下 XML 配置:spring-doc.cadn.net.cn

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

Spring 容器创建了一个AppPreferencesbean 通过使用appPreferencesbean 定义一次。也就是说,appPreferencesbean 的作用域为ServletContext级别并存储为常规ServletContext属性。这有点类似于 Spring 单例 bean,但是 在两个重要方面有所不同: 它是单例 perServletContext,而不是每个弹簧ApplicationContext(在任何给定的 Web 应用程序中可能有多个), 它实际上是公开的,因此可见为ServletContext属性。spring-doc.cadn.net.cn

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

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

WebSocket 作用域

WebSocket 作用域与 WebSocket 会话的生命周期相关联,适用于STOMP over WebSocket 应用程序,请参阅 WebSocket 作用域了解更多详细信息。spring-doc.cadn.net.cn

作用域 Bean 作为依赖项

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

您还可以使用<aop:scoped-proxy/>在作用域为singleton, 引用,然后通过可序列化的中间代理因此能够在反序列化时重新获取目标单例 Bean。spring-doc.cadn.net.cn

声明时<aop:scoped-proxy/>针对作用域的 Beanprototype,每个方法调用都会导致创建一个新的目标实例,然后将调用转发到该实例。spring-doc.cadn.net.cn

此外,作用域代理并不是以生命周期安全方式从较短作用域访问 Bean 的唯一方法。您也可以将注入点(即构造函数或 setter 参数或自动连接字段)声明为ObjectFactory<MyTargetBean>, 允许getObject()调用每次按需检索当前实例需要时——无需保留实例或单独存储它。spring-doc.cadn.net.cn

作为扩展变体,您可以声明ObjectProvider<MyTargetBean>哪个交付 几个额外的访问变体,包括getIfAvailablegetIfUnique.spring-doc.cadn.net.cn

其 JSR-330 变体称为Provider并与Provider<MyTargetBean>声明和相应的get()调用每次检索尝试。 有关 JSR-330 整体的更多详细信息,请参阅此处spring-doc.cadn.net.cn

以下示例中的配置只有一行,但重要的是 了解背后的“为什么”和“如何”:spring-doc.cadn.net.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 定义代理的行。

要创建此类代理,请插入一个子代理<aop:scoped-proxy/>元素转换为 作用域 bean 定义(请参阅选择要创建的代理类型基于 XML 模式的配置)。spring-doc.cadn.net.cn

为什么 bean 的定义作用域为request,session和 custom-scope级别需要<aop:scoped-proxy/>常见场景中的元素?考虑以下单例 bean 定义,并将其与您需要为上述范围定义的内容(请注意,以下内容userPreferencesbean 定义目前是不完整的):spring-doc.cadn.net.cn

<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). 这里的突出点是userManagerbean 是一个单例:它每容器及其依赖项(在本例中只有一个,即userPreferencesbean)是也只注入一次。这意味着userManagerbean 仅在完全相同userPreferences对象(即最初注入它的对象)。spring-doc.cadn.net.cn

这不是将寿命较短的作用域 Bean 注入寿命较长的作用域 Bean(例如,注入 HTTPSession-范围协作bean 作为依赖项添加到单例 bean 中)。相反,您需要一个userManager对象,并且,在 HTTP 的生命周期内Session,您需要一个userPreferences对象 特定于 HTTPSession. 因此,容器创建一个对象,该对象公开与UserPreferences类(理想情况下是对象,该对象是UserPreferencesinstance),它可以获取实数UserPreferences对象(HTTP 请求、Session,依此类推forth)。容器将此代理对象注入到userManagerbean,这是不知道这个UserPreferencesreference 是一个代理。在此示例中,当UserManager实例调用依赖注入的方法UserPreferences对象,它实际上是在调用代理上的方法。然后代理获取实数UserPreferences对象来自(在本例中)HTTPSession并将method 调用委托到检索到的实数上UserPreferences对象。spring-doc.cadn.net.cn

因此,在注入时需要以下(正确且完整的)配置request-session-scopedbean 转换为协作对象,如以下示例所示 显示:spring-doc.cadn.net.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 容器为标记为 这<aop:scoped-proxy/>元素,则创建基于 CGLIB 的类代理。spring-doc.cadn.net.cn

CGLIB 代理不会拦截私有方法。尝试调用私有方法 在这样的代理上,不会委托给实际作用域的目标对象。spring-doc.cadn.net.cn

或者,您可以配置 Spring 容器以创建标准 JDK 此类作用域 Bean 的基于接口的代理,通过指定false对于 这proxy-target-class属性的<aop:scoped-proxy/>元素。 使用 JDK基于接口的代理意味着您不需要在应用程序类路径中添加额外的库来影响此类代理。但是,这也意味着作用域 Bean 必须至少实现一个接口,并且所有协作者注入作用域 Bean 必须通过其 接口。 以下示例显示了基于接口的代理:spring-doc.cadn.net.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.cadn.net.cn

直接注入请求/会话引用

作为工厂示波器的替代方案,弹簧WebApplicationContext还支持 注射HttpServletRequest,HttpServletResponse,HttpSession,WebRequest和(如果存在 JSF)FacesContextExternalContext到 Spring 管理的 bean,只需通过常规注入旁边的基于类型的自动装配 其他豆的积分。Spring 通常为此类请求和会话注入代理 objects 的优点是在单例 bean 和可序列化 bean 中工作 同样,类似于工厂作用域 Bean 的作用域代理。spring-doc.cadn.net.cn

自定义范围

bean 作用域机制是可扩展的。您可以定义自己的 范围,甚至重新定义现有范围,尽管后者被认为是不好的做法 并且您不能覆盖内置的singletonprototype范围。spring-doc.cadn.net.cn

创建自定义作用域

要将自定义范围集成到 Spring 容器中,您需要实现org.springframework.beans.factory.config.Scope接口,在此 部分。有关如何实现您自己的范围的想法,请参阅ScopeSpring Framework 本身提供的实现和Scopejavadoc, 它更详细地解释了您需要实现的方法。spring-doc.cadn.net.cn

Scope接口有四种方法从作用域中获取对象,从 范围,让它们被摧毁。spring-doc.cadn.net.cn

例如,会话作用域实现返回会话作用域的 bean(如果它 不存在,则该方法在将 bean 绑定到 会议以备将来参考)。以下方法从 基础范围:spring-doc.cadn.net.cn

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

例如,会话作用域实现从底层会话中删除会话作用域的 bean。应该返回该对象,但您可以返回null如果找不到具有指定名称的对象。以下方法从底层作用域中删除对象:spring-doc.cadn.net.cn

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

以下方法注册一个回调,作用域应调用该回调destroyed 或当作用域中的指定对象被销毁时:spring-doc.cadn.net.cn

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

有关销毁回调的更多信息,请参阅 javadoc 或 Spring 作用域实现。spring-doc.cadn.net.cn

以下方法获取基础作用域的对话标识符:spring-doc.cadn.net.cn

String getConversationId()
fun getConversationId(): String

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

使用自定义作用域

编写并测试一个或多个自定义Scope实现,您需要使 Spring 容器知道你的新作用域。以下方法是中心 注册新Scope使用 Spring 容器:spring-doc.cadn.net.cn

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

此方法在ConfigurableBeanFactory接口,该接口可用 通过BeanFactory大部分混凝土的属性ApplicationContextSpring 附带的实现。spring-doc.cadn.net.cn

第一个参数registerScope(..)method 是与 一个范围。Spring 容器本身中此类名称的示例是singletonprototype.第二个参数registerScope(..)method 是一个实际实例 的Scope您希望注册和使用的实现。spring-doc.cadn.net.cn

假设你编写了自定义Scope实现,然后如图所示注册 在下一个示例中。spring-doc.cadn.net.cn

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

然后,您可以创建符合自定义范围规则的 Bean 定义Scope如下:spring-doc.cadn.net.cn

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

使用自定义Scope实施,您不仅限于程序化注册 范围。您还可以执行Scope注册,通过使用CustomScopeConfigurer类,如以下示例所示:spring-doc.cadn.net.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>
当您放置<aop:scoped-proxy/><bean>声明FactoryBean实现时,作用域的是工厂 bean 本身,而不是对象 返回自getObject().