Bean Scopes

当创建 Bean 定义时,实际上你是在为创建该 Bean 定义所定义的类的实际实例创建步骤。Bean 定义是步骤这一思想很重要,因为这意味着,就像对一个类一样,你可以从一个步骤创建多个对象实例。

When you create a bean definition, you create a recipe for creating actual instances of the class defined by that bean definition. The idea that a bean definition is a recipe is important, because it means that, as with a class, you can create many object instances from a single recipe.

你不仅可以控制要插入到从特定 Bean 定义创建的对象中的各种依赖性和配置值,还可以控制从特定 Bean 定义创建的对象的范围。这种方法非常强大且灵活,因为你可以通过配置选择你创建的对象的范围,而不是必须在 Java 类级别设置对象的范围。可以将 Bean 定义为在多个范围之一中进行部署。Spring Framework 支持六个范围,其中四个仅在你使用一个 Web 感知 ApplicationContext 时可用。你还可以创建 a custom scope.

You can control not only the various dependencies and configuration values that are to be plugged into an object that is created from a particular bean definition but also control the scope of the objects created from a particular bean definition. This approach is powerful and flexible, because you can choose the scope of the objects you create through configuration instead of having to bake in the scope of an object at the Java class level. Beans can be defined to be deployed in one of a number of scopes. The Spring Framework supports six scopes, four of which are available only if you use a web-aware ApplicationContext. You can also create a custom scope.

下表描述了受支持的作用域:

The following table describes the supported scopes:

Table 1. Bean scopes
Scope Description

singleton

(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.

prototype

Scopes a single bean definition to any number of object instances.

request

Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.

session

Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.

application

Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.

websocket

Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.

提供一个线程范围,但默认情况下不会注册该范围。欲了解更多信息,请参阅https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/support/SimpleThreadScope.html[SimpleThreadScope]的文档。有关如何注册此范围或任何其他自定义范围的说明,请参阅 Using a Custom Scope

A thread scope is available but is not registered by default. For more information, see the documentation for SimpleThreadScope. For instructions on how to register this or any other custom scope, see Using a Custom Scope.

The Singleton Scope

单例 bean 只有一个共享的实例受到管理,而且对 ID 与该 bean 定义匹配的 bean 的所有请求都会导致 Spring 容器返回那个特定的 bean 实例。

Only one shared instance of a singleton bean is managed, and all requests for beans with an ID or IDs that match that bean definition result in that one specific bean instance being returned by the Spring container.

换句话说,当您定义一个 bean 定义且其范围设为单例时,Spring IoC 容器会确切创建一个由该 bean 定义指定的对象实例。此单个实例会存储在这样一个单例 bean 的缓存中,而且对名为 bean 的所有后续请求和引用都会返回缓存的对象。下图展示了单例范围的工作原理:

To put it another way, when you define a bean definition and it is scoped as a singleton, the Spring IoC container creates exactly one instance of the object defined by that bean definition. This single instance is stored in a cache of such singleton beans, and all subsequent requests and references for that named bean return the cached object. The following image shows how the singleton scope works:

singleton

Spring 的单例 bean 的概念与四人帮 (GoF) 模式手册中定义的单例模式不同。GoF 单例硬编码了一个对象的范围,以便只创建特定类的唯一一个实例,每个类加载器也是仅有一个实例。Spring 单例的范围最恰当地描述为每个容器,每个 bean。这意味着,如果您在一个单独的 Spring 容器中为特定类定义了一个 bean,则 Spring 容器会确切创建一个由该 bean 定义所定义的类的实例,而且仅创建一个。单例范围是 Spring 中的默认范围。要在 XML 中将一个 bean 定义为单例,您可以创建一个 bean,如以下示例所示:

Spring’s concept of a singleton bean differs from the singleton pattern as defined in the Gang of Four (GoF) patterns book. The GoF singleton hard-codes the scope of an object such that one and only one instance of a particular class is created per ClassLoader. The scope of the Spring singleton is best described as being per-container and per-bean. This means that, if you define one bean for a particular class in a single Spring container, the Spring container creates one and only one instance of the class defined by that bean definition. The singleton scope is the default scope in Spring. To define a bean as a singleton in XML, you can define a bean as shown in the following example:

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

The Prototype Scope

bean 部署的非单例原型范围会在每次为特定的 bean 创建一个新的 bean 实例时产生。也就是说,该 bean 会注入到另一 bean 中,或者您可以通过容器上的 getBean() 方法调用请求它。作为规则,您应该将原型范围用于所有有状态的 bean,并将单例范围用于无状态的 bean。

The non-singleton prototype scope of bean deployment results in the creation of a new bean instance every time a request for that specific bean is made. That is, the bean is injected into another bean or you request it through a getBean() method call on the container. As a rule, you should use the prototype scope for all stateful beans and the singleton scope for stateless beans.

下图展示了 Spring 原型范围:

The following diagram illustrates the Spring prototype scope:

prototype

(数据访问对象 (DAO) 通常不会配置为原型,因为典型的 DAO 不会持有任何会话状态。我们更容易重复使用单例图的核心。)

(A data access object (DAO) is not typically configured as a prototype, because a typical DAO does not hold any conversational state. It was easier for us to reuse the core of the singleton diagram.)

以下示例在 XML 中将一个 bean 定义为原型:

The following example defines a bean as a prototype in XML:

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

与其他范围相反,Spring 不会管理原型 Bean 的完整生命周期。容器实例化、配置和以其他方式组装一个原型对象,并将其传递给客户端,而不进一步记录该原型实例。因此,尽管在所有对象上调用初始化生命周期回调方法,无论范围如何,在原型的情况下,不会调用已配置的销毁生命周期回调。客户端代码必须清理原型作用域的对象并释放原型 Bean 持有的昂贵资源。要让 Spring 容器释放原型作用域 Bean 持有的资源,请尝试使用包含对需要清理的 Bean 的引用的自定义 bean post-processor

In contrast to the other scopes, Spring does not manage the complete lifecycle of a prototype bean. The container instantiates, configures, and otherwise assembles a prototype object and hands it to the client, with no further record of that prototype instance. Thus, although initialization lifecycle callback methods are called on all objects regardless of scope, in the case of prototypes, configured destruction lifecycle callbacks are not called. The client code must clean up prototype-scoped objects and release expensive resources that the prototype beans hold. To get the Spring container to release resources held by prototype-scoped beans, try using a custom bean post-processor which holds a reference to beans that need to be cleaned up.

在某些方面,Spring 容器在原型范围 Bean 方面的作用是 Java new 运算符的替代品。此点之后的全部生命周期管理都必须由客户端处理。(有关 Spring 容器中 Bean 的生命周期的详细信息,请参阅 Lifecycle Callbacks。)

In some respects, the Spring container’s role in regard to a prototype-scoped bean is a replacement for the Java new operator. All lifecycle management past that point must be handled by the client. (For details on the lifecycle of a bean in the Spring container, see Lifecycle Callbacks.)

Singleton Beans with Prototype-bean Dependencies

当您对单例范围 bean 使用对原型 bean 的依赖项时,请注意在实例化时解决依赖项。因此,如果您将原型范围的 bean 依赖性注入到单例范围的 bean 中,则会实例化一个新的原型 bean,然后将其依赖性注入到单例 bean 中。该原型实例是唯一一直提供给单例范围的 bean 的实例。

When you use singleton-scoped beans with dependencies on prototype beans, be aware that dependencies are resolved at instantiation time. Thus, if you dependency-inject a prototype-scoped bean into a singleton-scoped bean, a new prototype bean is instantiated and then dependency-injected into the singleton bean. The prototype instance is the sole instance that is ever supplied to the singleton-scoped bean.

但是,假设您想要让单例范围的 bean 在运行时重复获取样例范围 bean 的新实例。您不能将样例范围的 bean 依赖注入到您的单例 bean 中,因为当 Spring 容器实例化单例 bean 并且解决并注入其依赖项时,这种注入仅发生一次。如果您在运行时需要一个样例 bean 的新实例多次,请参阅 Method Injection

However, suppose you want the singleton-scoped bean to acquire a new instance of the prototype-scoped bean repeatedly at runtime. You cannot dependency-inject a prototype-scoped bean into your singleton bean, because that injection occurs only once, when the Spring container instantiates the singleton bean and resolves and injects its dependencies. If you need a new instance of a prototype bean at runtime more than once, see Method Injection.

Request, Session, Application, and WebSocket Scopes

requestsessionapplicationwebsocket 范围仅在您使用 web 感知的 Spring ApplicationContext 实现(如 XmlWebApplicationContext)时可用。如果您将这些范围与常规 Spring IoC 容器(如 ClassPathXmlApplicationContext)一起使用,则会抛出一个 IllegalStateException,指出未知的 bean 范围。

The request, session, application, and websocket scopes are available only if you use a web-aware Spring ApplicationContext implementation (such as XmlWebApplicationContext). If you use these scopes with regular Spring IoC containers, such as the ClassPathXmlApplicationContext, an IllegalStateException that complains about an unknown bean scope is thrown.

Initial Web Configuration

为了在 requestsessionapplicationwebsocket 级别(基于 web 的 bean)支持 bean 的范围(基于 web 的 bean),在定义 bean 之前进行一些小的初始配置是必需的。(对于标准范围(singletonprototype),不需要此初始设置。)

To support the scoping of beans at the request, session, application, and websocket levels (web-scoped beans), some minor initial configuration is required before you define your beans. (This initial setup is not required for the standard scopes: singleton and prototype.)

您如何实现此初始设置取决于您特定的 Servlet 环境。

How you accomplish this initial setup depends on your particular Servlet environment.

如果您在 Spring Web MVC 内访问范围 bean,实际上是在 Spring DispatcherServlet 处理的请求内,不需要特殊设置。DispatcherServlet 已公开所有相关状态。

If you access scoped beans within Spring Web MVC, in effect, within a request that is processed by the Spring DispatcherServlet, no special setup is necessary. DispatcherServlet already exposes all relevant state.

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

If you use a Servlet web container, with requests processed outside of Spring’s DispatcherServlet (for example, when using JSF), you need to register the org.springframework.web.context.request.RequestContextListener ServletRequestListener. This can be done programmatically by using the WebApplicationInitializer interface. Alternatively, add the following declaration to your web application’s web.xml file:

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

或者,如果您的侦听器设置有问题,请考虑使用 Spring 的 RequestContextFilter。过滤器映射取决于周围的 web 应用程序配置,因此您必须对其进行适当的更改。以下清单展示了 web 应用程序的过滤器部分:

Alternatively, if there are issues with your listener setup, consider using Spring’s RequestContextFilter. The filter mapping depends on the surrounding web application configuration, so you have to change it as appropriate. The following listing shows the filter part of a web application:

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

DispatcherServletRequestContextListenerRequestContextFilter 都执行完全相同的事务,即将 HTTP 请求对象绑定到为该请求提供服务的 Thread。这使得在后续的调用链中可使用受 request 和 session 范围约束的 bean。

DispatcherServlet, RequestContextListener, and RequestContextFilter all do exactly the same thing, namely bind the HTTP request object to the Thread that is servicing that request. This makes beans that are request- and session-scoped available further down the call chain.

Request scope

考虑以下 XML 配置,用于 bean 定义:

Consider the following XML configuration for a bean definition:

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

Spring 容器对于每一次 HTTP 请求,通过使用 loginAction bean 定义来创建 LoginAction bean 的一个新实例。也就是说,loginAction bean 的作用域为 HTTP 请求级别。您可以根据需要更改实例的内部状态,因为从同一 loginAction bean 定义创建的其他实例看不到这些状态变化。它是针对个人请求而言的。当请求处理完成后,作用域在请求中的 bean 会被丢弃。

The Spring container creates a new instance of the LoginAction bean by using the loginAction bean definition for each and every HTTP request. That is, the loginAction bean is scoped at the HTTP request level. You can change the internal state of the instance that is created as much as you want, because other instances created from the same loginAction bean definition do not see these changes in state. They are particular to an individual request. When the request completes processing, the bean that is scoped to the request is discarded.

在使用注解驱动的组件或 Java 配置时,可以使用 @RequestScope 注解将一个组件分配给 request 作用域。以下示例说明了如何执行此操作:

When using annotation-driven components or Java configuration, the @RequestScope annotation can be used to assign a component to the request scope. The following example shows how to do so:

  • Java

  • Kotlin

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

Session Scope

考虑以下 XML 配置,用于 bean 定义:

Consider the following XML configuration for a bean definition:

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

Spring 容器对于一个 HTTP Session 的生命周期,通过使用 userPreferences bean 定义来创建一个 UserPreferences bean 的新实例。换句话说,userPreferences bean 的作用域实际上是 HTTP Session 级别。对于采用请求作用域的 bean,您可以根据需要更改实例的内部状态,了解使用相同 userPreferences bean 定义创建的其他人 HTTP Session 实例不会看到这些状态变化,因为它们针对单个 HTTP Session 而言。当 HTTP Session 最终被丢弃时,作用域在该特定 HTTP Session 中的 bean 也将被丢弃。

The Spring container creates a new instance of the UserPreferences bean by using the userPreferences bean definition for the lifetime of a single HTTP Session. In other words, the userPreferences bean is effectively scoped at the HTTP Session level. As with request-scoped beans, you can change the internal state of the instance that is created as much as you want, knowing that other HTTP Session instances that are also using instances created from the same userPreferences bean definition do not see these changes in state, because they are particular to an individual HTTP Session. When the HTTP Session is eventually discarded, the bean that is scoped to that particular HTTP Session is also discarded.

在使用注解驱动的组件或 Java 配置时,您可以使用 @SessionScope 注解将一个组件分配给 session 作用域。

When using annotation-driven components or Java configuration, you can use the @SessionScope annotation to assign a component to the session scope.

  • Java

  • Kotlin

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

Application Scope

考虑以下 XML 配置,用于 bean 定义:

Consider the following XML configuration for a bean definition:

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

Spring 容器对于整个 Web 应用程序,通过使用 appPreferences bean 定义来创建 AppPreferences bean 的一个新实例。也就是说,appPreferences bean 的作用域为 ServletContext 级别并存储为常规 ServletContext 属性。这在某种程度上类似于 Spring 单例 bean,但有两种重要的区别:它对于每个 ServletContext 为单例,对于每个 Spring ApplicationContext 而言不是(其中在任何给定 Web 应用程序中可能有多个),而且它实际暴露,因此可见为 ServletContext 属性。

The Spring container creates a new instance of the AppPreferences bean by using the appPreferences bean definition once for the entire web application. That is, the appPreferences bean is scoped at the ServletContext level and stored as a regular ServletContext attribute. This is somewhat similar to a Spring singleton bean but differs in two important ways: It is a singleton per ServletContext, not per Spring ApplicationContext (for which there may be several in any given web application), and it is actually exposed and therefore visible as a ServletContext attribute.

在使用注解驱动的组件或 Java 配置时,您可以使用 @ApplicationScope 注解将一个组件分配给 application 作用域。以下示例说明了如何执行此操作:

When using annotation-driven components or Java configuration, you can use the @ApplicationScope annotation to assign a component to the application scope. The following example shows how to do so:

  • Java

  • Kotlin

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

WebSocket Scope

WebSocket 范围与 WebSocket 会话的生命周期关联,并适用于通过 WebSocket 的 STOMP,有关更多详细信息,请参阅“WebSocket scope”。

WebSocket scope is associated with the lifecycle of a WebSocket session and applies to STOMP over WebSocket applications, see WebSocket scope for more details.

Scoped Beans as Dependencies

Spring IoC 容器不仅管理您的对象(bean)的实例化,还管理协作者(或依赖项)的连接。如果您想将(例如)HTTP 请求作用域 bean 注入另一个生命周期更长的 bean,您可以选择注入一个 AOP 代理以代替作用域 bean。也就是说,您需要注入一个代理对象,它公开与作用域对象相同的公共接口,而且还可以从相关作用域(例如 HTTP 请求)检索真实目标对象并将方法调用委托到真实对象。

The Spring IoC container manages not only the instantiation of your objects (beans), but also the wiring up of collaborators (or dependencies). If you want to inject (for example) an HTTP request-scoped bean into another bean of a longer-lived scope, you may choose to inject an AOP proxy in place of the scoped bean. That is, you need to inject a proxy object that exposes the same public interface as the scoped object but that can also retrieve the real target object from the relevant scope (such as an HTTP request) and delegate method calls onto the real object.

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

You may also use <aop:scoped-proxy/> between beans that are scoped as singleton, with the reference then going through an intermediate proxy that is serializable and therefore able to re-obtain the target singleton bean on deserialization.

在针对作用域为 prototype 的 bean 声明 <aop:scoped-proxy/> 时,对共享代理的每次方法调用都会导致创建一个新的目标实例,然后将调用转发到该实例。

When declaring <aop:scoped-proxy/> against a bean of scope prototype, every method call on the shared proxy leads to the creation of a new target instance to which the call is then being forwarded.

此外,作用域代理不是以生命周期安全的方式访问较短作用域中 bean 的唯一方法。您还可以将注入点(即构造函数或 setter 参数或自动注入的字段)声明为 ObjectFactory<MyTargetBean>,从而允许 getObject() 调用在每次需要时按需检索当前实例——而不持有该实例或单独存储它。

Also, scoped proxies are not the only way to access beans from shorter scopes in a lifecycle-safe fashion. You may also declare your injection point (that is, the constructor or setter argument or autowired field) as ObjectFactory<MyTargetBean>, allowing for a getObject() call to retrieve the current instance on demand every time it is needed — without holding on to the instance or storing it separately.

作为扩展变体,您可以声明 ObjectProvider<MyTargetBean>,它提供了其他访问变体,包括 getIfAvailablegetIfUnique

As an extended variant, you may declare ObjectProvider<MyTargetBean> which delivers several additional access variants, including getIfAvailable and getIfUnique.

此类的 JSR-330 变量称为 Provider,在每次检索尝试中都会使用它以及 `Provider<MyTargetBean>`声明和对应的 `get()`调用。有关 JSR-330 整体的详细信息,请参阅 here

The JSR-330 variant of this is called Provider and is used with a Provider<MyTargetBean> declaration and a corresponding get() call for every retrieval attempt. See here for more details on JSR-330 overall.

以下示例中的配置只有一行,但理解它的 “为什么” 和 “如何” 非常重要:

The configuration in the following example is only one line, but it is important to understand the “why” as well as the “how” behind it:

<?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 The line that defines the proxy.

要创建此代理,你可以将一个子 <aop:scoped-proxy/> 元素插入到作用域 Bean 定义中(请参阅 Choosing the Type of Proxy to CreateXML Schema-based configuration)。

To create such a proxy, you insert a child <aop:scoped-proxy/> element into a scoped bean definition (see Choosing the Type of Proxy to Create and XML Schema-based configuration).

为什么在一般场景中,作用域为 requestsession 和自定义作用域级别的 bean 定义需要 <aop:scoped-proxy/> 元素?考虑以下单例 bean 定义并将其与为上述作用域定义的内容进行对比(请注意,以下 userPreferences bean 定义按实际情况是不完整的):

Why do definitions of beans scoped at the request, session and custom-scope levels require the <aop:scoped-proxy/> element in common scenarios? Consider the following singleton bean definition and contrast it with what you need to define for the aforementioned scopes (note that the following userPreferences bean definition as it stands is incomplete):

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

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

在前面的示例中,单例 bean(userManager)注入了一个对 HTTP Session 作用域 bean(userPreferences)的引用。这里的重要点是 userManager bean 是一个单例:它对于每个容器实例化一次,而且它的依赖项(在此场景中只有一个,即 userPreferences bean)仅注入一次。这意味着 userManager bean 仅对完全相同的 userPreferences 对象进行操作(即最初为其注入的对象)。

In the preceding example, the singleton bean (userManager) is injected with a reference to the HTTP Session-scoped bean (userPreferences). The salient point here is that the userManager bean is a singleton: it is instantiated exactly once per container, and its dependencies (in this case only one, the userPreferences bean) are also injected only once. This means that the userManager bean operates only on the exact same userPreferences object (that is, the one with which it was originally injected).

将作用域较短的 bean 注入到作用域较长的 bean 中时,这不是您想要的(例如,将 HTTP Session 作用域的协作 bean 注入单例 bean 作为依赖项)。相反,您需要一个单独的 userManager 对象,而且对于 HTTP Session 的生命周期,您需要一个特定于 HTTP SessionuserPreferences 对象。因此,容器会创建一个对象,它公开与 UserPreferences 类完全相同的公共接口(理想情况下是一个 UserPreferences 实例的对象),哪个可以通过作用域机制(HTTP 请求、Session 等)获取真实 UserPreferences 对象。容器将此代理对象注入到 userManager bean 中,而 userManager 意识不到这个 UserPreferences 引用是代理。在此示例中,当 UserManager 实例在依赖项注入的 UserPreferences 对象上调用方法时,它实际上是在对代理调用方法。然后,代理从(在此场景中)HTTP Session 获取真实的 UserPreferences 对象,并将方法调用委托给检索到的真实 UserPreferences 对象。

This is not the behavior you want when injecting a shorter-lived scoped bean into a longer-lived scoped bean (for example, injecting an HTTP Session-scoped collaborating bean as a dependency into singleton bean). Rather, you need a single userManager object, and, for the lifetime of an HTTP Session, you need a userPreferences object that is specific to the HTTP Session. Thus, the container creates an object that exposes the exact same public interface as the UserPreferences class (ideally an object that is a UserPreferences instance), which can fetch the real UserPreferences object from the scoping mechanism (HTTP request, Session, and so forth). The container injects this proxy object into the userManager bean, which is unaware that this UserPreferences reference is a proxy. In this example, when a UserManager instance invokes a method on the dependency-injected UserPreferences object, it is actually invoking a method on the proxy. The proxy then fetches the real UserPreferences object from (in this case) the HTTP Session and delegates the method invocation onto the retrieved real UserPreferences object.

因此,在将 requestsession 作用域的 bean 注入到协作对象时,您需要以下(正确且完整)的配置,如下例所示:

Thus, you need the following (correct and complete) configuration when injecting request- and session-scoped beans into collaborating objects, as the following example shows:

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

Choosing the Type of Proxy to Create

默认情况下,当 Spring 容器为标记有 <aop: scoped-proxy/> 元素的 bean 创建代理时,便会创建一个基于 CGLIB 的类代理。

By default, when the Spring container creates a proxy for a bean that is marked up with the <aop:scoped-proxy/> element, a CGLIB-based class proxy is created.

CGLIB 代理不会拦截私有方法。尝试在此类代理上调用私有方法不会委托给实际的范围目标对象。

CGLIB proxies do not intercept private methods. Attempting to call a private method on such a proxy will not delegate to the actual scoped target object.

或者,可以通过为 <aop: scoped-proxy/> 元素的 proxy-target-class 属性指定 false 值来配置 Spring 容器,使其针对此类范围 bean 创建基于标准 JDK 接口的代理。使用基于 JDK 接口的代理意味着您无需在应用程序类路径中添加额外的库即可影响此类代理。然而,这也意味着范围 bean 的类必须至少实现一个接口,并且将范围 bean 注入到的所有协作方都必须通过其某个接口引用该 bean。以下示例展示了基于接口的代理:

Alternatively, you can configure the Spring container to create standard JDK interface-based proxies for such scoped beans, by specifying false for the value of the proxy-target-class attribute of the <aop:scoped-proxy/> element. Using JDK interface-based proxies means that you do not need additional libraries in your application classpath to affect such proxying. However, it also means that the class of the scoped bean must implement at least one interface and that all collaborators into which the scoped bean is injected must reference the bean through one of its interfaces. The following example shows a proxy based on an interface:

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

有关选择基于类或基于接口的代理的详细信息,请参阅 Proxying Mechanisms

For more detailed information about choosing class-based or interface-based proxying, see Proxying Mechanisms.

Injecting Request/Session References Directly

作为对工厂范围的替代,Spring WebApplicationContext 还支持将 HttpServletRequestHttpServletResponseHttpSessionWebRequest(如果存在 JSF)FacesContextExternalContext 注入到 Spring 管理的 bean 中,只需在其常规注入点旁边进行基于类型的自动装配即可,针对其他 bean 也是如此。通常,Spring 会为这些请求和会话对象注入代理,这些代理和针对工厂范围的 bean 的范围代理类似,优势在于也能够在单例 bean 和可序列化 bean 中工作。

As an alternative to factory scopes, a Spring WebApplicationContext also supports the injection of HttpServletRequest, HttpServletResponse, HttpSession, WebRequest and (if JSF is present) FacesContext and ExternalContext into Spring-managed beans, simply through type-based autowiring next to regular injection points for other beans. Spring generally injects proxies for such request and session objects which has the advantage of working in singleton beans and serializable beans as well, similar to scoped proxies for factory-scoped beans.

Custom Scopes

bean 范围机制是可扩展的。您可以定义自己的范围,甚至可以重新定义现有范围,不过后者被认为是不良做法,而且您无法覆盖内置的 singletonprototype 范围。

The bean scoping mechanism is extensible. You can define your own scopes or even redefine existing scopes, although the latter is considered bad practice and you cannot override the built-in singleton and prototype scopes.

Creating a Custom Scope

要将自定义作用域集成到 Spring 容器中,您需要实现 org.springframework.beans.factory.config.Scope`接口,本节对此进行了描述。关于如何实现您自己的作用域,请参阅与 Spring 框架本身和 `Scope javadoc 一起提供的 `Scope`实现,其中更详细地解释了您需要实现的方法。

To integrate your custom scopes into the Spring container, you need to implement the org.springframework.beans.factory.config.Scope interface, which is described in this section. For an idea of how to implement your own scopes, see the Scope implementations that are supplied with the Spring Framework itself and the Scope javadoc, which explains the methods you need to implement in more detail.

Scope 接口有四种方法来从该范围内获取对象、将其从该范围移除,并让它们被销毁。

The Scope interface has four methods to get objects from the scope, remove them from the scope, and let them be destroyed.

例如,会话范围实现返回会话范围的 bean(如果这个 bean 不存在,则该方法返回该 bean 的一个新实例,同时让它绑定到该会话,以供将来参考)。以下方法从底层范围内返回对象:

The session scope implementation, for example, returns the session-scoped bean (if it does not exist, the method returns a new instance of the bean, after having bound it to the session for future reference). The following method returns the object from the underlying scope:

  • Java

  • Kotlin

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

例如,会话范围实现从底层会话中移除会话范围的 bean。应当返回对象,但如果未找到指定名称的对象,则您可以返回 null。以下方法从底层范围内移除对象:

The session scope implementation, for example, removes the session-scoped bean from the underlying session. The object should be returned, but you can return null if the object with the specified name is not found. The following method removes the object from the underlying scope:

  • Java

  • Kotlin

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

以下方法注册回调,该回调应当在该范围被销毁或该范围中的指定对象被销毁时被调用:

The following method registers a callback that the scope should invoke when it is destroyed or when the specified object in the scope is destroyed:

  • Java

  • Kotlin

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

有关销毁回调的详细信息,请参阅 javadoc 或 Spring 作用域实现。

See the javadoc or a Spring scope implementation for more information on destruction callbacks.

以下方法获取底层范围的对话标识符:

The following method obtains the conversation identifier for the underlying scope:

  • Java

  • Kotlin

String getConversationId()
fun getConversationId(): String

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

This identifier is different for each scope. For a session scoped implementation, this identifier can be the session identifier.

Using a Custom Scope

编写并测试了一个或多个自定义 Scope 实现后,您需要让 Spring 容器知道您的新范围。以下方法是向 Spring 容器注册新 Scope 的核心方法:

After you write and test one or more custom Scope implementations, you need to make the Spring container aware of your new scopes. The following method is the central method to register a new Scope with the Spring container:

  • Java

  • Kotlin

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

此方法在 ConfigurableBeanFactory 接口上声明,该接口可通过 Spring 附带的大多数具体 ApplicationContext 实现上的 BeanFactory 属性进行访问。

This method is declared on the ConfigurableBeanFactory interface, which is available through the BeanFactory property on most of the concrete ApplicationContext implementations that ship with Spring.

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

The first argument to the registerScope(..) method is the unique name associated with a scope. Examples of such names in the Spring container itself are singleton and prototype. The second argument to the registerScope(..) method is an actual instance of the custom Scope implementation that you wish to register and use.

假设您编写了您的自定义 Scope 实现,然后按下一个示例所示进行注册。

Suppose that you write your custom Scope implementation, and then register it as shown in the next example.

下一个示例使用 SimpleThreadScope,该示例包含在 Spring 中,但默认情况下不会注册。说明对于您自己的自定义 Scope 实现来说是相同的。

The next example uses SimpleThreadScope, which is included with Spring but is not registered by default. The instructions would be the same for your own custom Scope implementations.

  • Java

  • Kotlin

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

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

You can then create bean definitions that adhere to the scoping rules of your custom Scope, as follows:

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

使用自定义的 Scope 实现,你不受 Scope 的编程注册的限制。你也可以使用 CustomScopeConfigurer 类声明式地注册 Scope,如下面的示例所示:

With a custom Scope implementation, you are not limited to programmatic registration of the scope. You can also do the Scope registration declaratively, by using the CustomScopeConfigurer class, as the following example shows:

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

当你将 @{1} 放置在 @{2} 为 @{3} 实现的声明中时,实际上范围化的是工厂 bean 本身,而不是 @{4} 返回的对象。

When you place <aop:scoped-proxy/> within a <bean> declaration for a FactoryBean implementation, it is the factory bean itself that is scoped, not the object returned from getObject().