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

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

下表描述了支持的范围:

表 1.Bean 作用域
范围 描述

单身 人士

(默认)将单个 Bean 定义的范围限定为每个 Spring IoC 的单个对象实例 容器。

原型

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

请求

将单个 Bean 定义的范围限定为单个 HTTP 请求的生命周期。那是 每个 HTTP 请求都有自己的 Bean 实例,该实例是从单个 Bean 的背面创建的 Bean 定义。仅在 Web 感知 Spring 的上下文中有效。ApplicationContext

会期

将单个 Bean 定义的范围限定为 HTTP 的生命周期。仅在 网络感知 Spring 的背景。SessionApplicationContext

应用

将单个 Bean 定义的范围限定为 .仅在 网络感知 Spring 的背景。ServletContextApplicationContext

网络插座

将单个 Bean 定义的范围限定为 .仅在 网络感知 Spring 的背景。WebSocketApplicationContext

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

单例示波器

仅管理一个单一实例 Bean 的共享实例,并且所有对 Bean 的请求 具有与该 Bean 定义匹配的一个或多个 ID 将生成一个特定 Bean Spring 容器返回的实例。

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

单身 人士

Spring 的单例 Bean 概念不同于 四人帮 (GoF) 模式书。GoF 单例对 对象,使得每个特定类的一个且只有一个实例被创建 ClassLoader。Spring 单例的范围最好描述为每个容器 和每豆。这意味着,如果为 单个 Spring 容器,Spring 容器创建一个且只有一个实例 由该 Bean 定义定义的类。单一实例作用域是默认作用域 在春天。要在 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 实例。也就是说,豆子 注入到另一个 Bean 中,或者您通过对 容器。通常,您应该对所有有状态 Bean 使用 prototype 范围,并且 无状态 Bean 的单例作用域。getBean()

下图说明了 Spring 原型作用域:

原型

(数据访问对象 (DAO) 通常不配置为原型,因为典型的 DAO 不成立 任何对话状态。对我们来说,重用核心 单例图。

下面的示例将 Bean 定义为 XML 中的原型:

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

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

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

具有原型 Bean 依赖关系的单例 Bean

当您使用依赖于原型 Bean 的单例范围的 Bean 时,请注意 依赖项在实例化时解析。因此,如果依赖注入 原型作用域的 Bean 进入单例作用域的 Bean,实例化新的原型 Bean 然后将依赖注入到单例 Bean 中。原型实例是唯一的 曾经提供给单例范围的 Bean 的实例。

但是,假设您希望单例范围的 Bean 获取 原型范围的 Bean 在运行时重复。您不能依赖注入 原型范围的 Bean 到单例 Bean 中,因为注入只发生 一次,当 Spring 容器实例化单例 Bean 并解析 并注入其依赖项。如果您需要原型 Bean 的新实例,请在 运行时不止一次,请参阅方法注入

请求、会话、应用程序和 WebSocket 作用域

、 、 和 作用域仅可用 如果您使用 Web 感知的 Spring 实现(例如 )。如果将这些作用域与常规 Spring IoC 容器一起使用, 如 , 一个 抱怨 关于一个未知的 Bean 范围被抛出。requestsessionapplicationwebsocketApplicationContextXmlWebApplicationContextClassPathXmlApplicationContextIllegalStateException

初始 Web 配置

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

如何完成此初始设置取决于特定的 Servlet 环境。

如果您在 Spring Web MVC 中访问作用域 bean,实际上,在请求中 由弹簧处理,无需特殊设置。 已经公开了所有相关状态。DispatcherServletDispatcherServlet

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

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

或者,如果您的侦听器设置有问题,请考虑使用 Spring 的 .过滤器映射取决于周围的网络 应用程序配置,因此您必须根据需要对其进行更改。以下列表 显示 Web 应用程序的筛选器部分:RequestContextFilter

<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、 、 和 都 做 一 同样的事情,即将 HTTP 请求对象绑定到正在服务的 该请求。这使得请求范围和会话范围的 Bean 进一步可用 在呼叫链中向下。RequestContextListenerRequestContextFilterThread

请求范围

请考虑以下 Bean 定义的 XML 配置:

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

Spring 容器通过使用每个 HTTP 请求的 Bean 定义来创建一个 Bean 的新实例。也就是说,Bean 的作用域是 HTTP 请求级别。您可以更改内部 根据需要创建的实例的状态,因为其他实例 从同一 Bean 定义创建的 Bean 在状态中看不到这些更改。 它们特定于个人请求。当请求完成处理时, 范围限定为请求的 Bean 将被丢弃。LoginActionloginActionloginActionloginAction

使用注释驱动的组件或 Java 配置时,注释 可用于将组件分配给作用域。以下示例演示如何 为此,请执行以下操作:@RequestScoperequest

  • Java

  • Kotlin

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

会话范围

请考虑以下 Bean 定义的 XML 配置:

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

Spring 容器使用单个 HTTP 生存期的 Bean 定义创建 Bean 的新实例。在其他 换句话说,Bean 的范围有效地限定在 HTTP 级别。如 使用请求范围的 Bean,您可以更改实例的内部状态,即 随心所欲地创建,知道其他 HTTP 实例也 使用从同一 Bean 定义创建的实例不会看到这些 状态的变化,因为它们特定于单个 HTTP 。当 HTTP 最终会被丢弃,范围限定为该特定 HTTP 的 Bean 也会被丢弃。UserPreferencesuserPreferencesSessionuserPreferencesSessionSessionuserPreferencesSessionSessionSession

使用注释驱动的组件或 Java 配置时,可以使用注释将组件分配给作用域。@SessionScopesession

  • Java

  • Kotlin

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

应用范围

请考虑以下 Bean 定义的 XML 配置:

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

Spring 容器通过对整个 Web 应用程序使用 Bean 定义一次来创建 Bean 的新实例。也就是说,Bean 在级别上具有作用域,并存储为常规属性。这有点类似于 Spring 单例 bean,但 在两个重要方面有所不同:它是每个 的单例,而不是每个 Spring(在任何给定的 Web 应用程序中可能有多个), 它实际上是公开的,因此作为一个属性是可见的。AppPreferencesappPreferencesappPreferencesServletContextServletContextServletContextApplicationContextServletContext

使用注释驱动的组件或 Java 配置时,可以使用注释将组件分配给作用域。这 以下示例演示如何执行此操作:@ApplicationScopeapplication

  • Java

  • Kotlin

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

WebSocket 作用域

WebSocket 作用域与 WebSocket 会话的生命周期相关联,适用于 STOMP over WebSocket 应用程序,有关详细信息,请参阅 WebSocket 作用域

作用域内的 Bean 作为依赖项

Spring IoC 容器不仅管理对象 (bean) 的实例化, 还有协作者(或依赖关系)的接线。如果要注入(对于 example) 将一个 HTTP 请求范围的 Bean 放入另一个生存期较长的 Bean 中,您可以 选择注入 AOP 代理来代替作用域内的 Bean。也就是说,您需要注入 一个代理对象,该对象公开与作用域对象相同的公共接口,但可以 还从相关作用域(如HTTP请求)中检索实际目标对象 和 delegate 方法调用真实对象。

您还可以在范围为 的 Bean 之间使用 , 然后,引用通过可序列化的中间代理 因此能够在反序列化时重新获得目标单例 Bean。<aop:scoped-proxy/>singleton

当针对范围的 Bean 声明时,每个方法 调用共享代理会导致创建一个新的目标实例,该实例 然后转接呼叫。<aop:scoped-proxy/>prototype

此外,作用域代理并不是从较短作用域访问 Bean 的唯一方法 生命周期安全时尚。您也可以声明您的进样点(即 构造函数或 setter 参数或 autowired 字段)作为 , 允许调用以按需检索当前实例 需要时间,无需保留实例或单独存储实例。ObjectFactory<MyTargetBean>getObject()

作为扩展变体,您可以声明哪个交付 其他几个访问变体,包括 和 .ObjectProvider<MyTargetBean>getIfAvailablegetIfUnique

它的 JSR-330 变体被调用,并与声明一起使用,并为每次检索尝试提供相应的调用。 有关 JSR-330 的更多详细信息,请参阅此处ProviderProvider<MyTargetBean>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">

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

为什么 Bean 的定义作用域为 和 custom-scope 级别在常见场景中需要元素吗? 考虑以下单例 Bean 定义,并将其与 您需要为上述范围定义什么(请注意,以下 Bean 定义是不完整的):requestsession<aop:scoped-proxy/>userPreferences

<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 是一个单例:它每 容器及其依赖项(在本例中只有一个,即 bean)是 也只注射了一次。这意味着 Bean 仅在 完全相同的对象(即最初注入它的对象)。userManagerSessionuserPreferencesuserManageruserPreferencesuserManageruserPreferences

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

因此,在将 Bean 注入协作对象时,您需要以下(正确且完整)配置,如以下示例所示 显示:request-session-scoped

<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 创建代理时,该代理标记为 元素,创建一个基于 CGLIB 的类代理。<aop:scoped-proxy/>

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

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

<!-- 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 还支持 注入 、 、 、 和 (如果存在 JSF) 并注入 弹簧管理的豆子,只需通过常规注射旁边的基于类型的自动接线 其他豆类的积分。Spring 通常会为此类请求和会话注入代理 对象,其优点是在单例 Bean 和可序列化 Bean 中工作 同样,类似于工厂范围的 Bean 的作用域代理。WebApplicationContextHttpServletRequestHttpServletResponseHttpSessionWebRequestFacesContextExternalContext

自定义范围

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

创建自定义范围

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

该接口有四种方法从作用域中获取对象,从中删除它们 范围,并让它们被摧毁。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

以下方法注册一个回调,当作用域在 销毁或当范围内的指定对象被销毁时:

  • Java

  • Kotlin

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

有关销毁回调的更多信息,请参阅 javadoc 或 Spring scope 实现。

以下方法获取基础作用域的会话标识符:

  • Java

  • Kotlin

String getConversationId()
fun getConversationId(): String

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

使用自定义范围

编写并测试一个或多个自定义实现后,需要将 Spring 容器会识别您的新作用域。以下方法是核心方法 向 Spring 容器注册新容器的方法:ScopeScope

  • Java

  • Kotlin

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

此方法是在可用的接口上声明的 通过 Spring 附带的大多数具体实现上的属性。ConfigurableBeanFactoryBeanFactoryApplicationContext

该方法的第一个参数是与 范围。Spring 容器本身中此类名称的示例包括 和 。该方法的第二个参数是实际实例 您希望注册和使用的自定义实现。registerScope(..)singletonprototyperegisterScope(..)Scope

假设您编写自定义实现,然后注册它,如下所示 在下一个示例中。Scope

下一个示例使用 ,它包含在 Spring 中,但不是 默认注册。对于您自己的自定义实现,说明是相同的。SimpleThreadScopeScope
  • Java

  • Kotlin

Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
val threadScope = SimpleThreadScope()
beanFactory.registerScope("thread", threadScope)

然后,您可以创建符合自定义范围规则的 Bean 定义,如下所示:Scope

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

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

<?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()