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:
Scope | Description |
---|---|
(Default) Scopes a single bean definition to a single object instance for each Spring IoC container. |
|
Scopes a single bean definition to any number of object instances. |
|
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 |
|
Scopes a single bean definition to the lifecycle of an HTTP |
|
Scopes a single bean definition to the lifecycle of a |
|
Scopes a single bean definition to the lifecycle of a |
提供一个线程范围,但默认情况下不会注册该范围。欲了解更多信息,请参阅https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/support/SimpleThreadScope.html[ |
A thread scope is available but is not registered by default. For more information,
see the documentation for
|
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:
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:
(数据访问对象 (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
request
、session
、application
和 websocket
范围仅在您使用 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
为了在 request
、session
、application
和 websocket
级别(基于 web 的 bean)支持 bean 的范围(基于 web 的 bean),在定义 bean 之前进行一些小的初始配置是必需的。(对于标准范围(singleton
和 prototype
),不需要此初始设置。)
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>
DispatcherServlet
、RequestContextListener
和 RequestContextFilter
都执行完全相同的事务,即将 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.
您还可以在作用域为 You may also use 在针对作用域为 When declaring 此外,作用域代理不是以生命周期安全的方式访问较短作用域中 bean 的唯一方法。您还可以将注入点(即构造函数或 setter 参数或自动注入的字段)声明为 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 作为扩展变体,您可以声明 As an extended variant, you may declare 此类的 JSR-330 变量称为 The JSR-330 variant of this is called |
以下示例中的配置只有一行,但理解它的 “为什么” 和 “如何” 非常重要:
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 Create 和 XML 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).
为什么在一般场景中,作用域为 request
、session
和自定义作用域级别的 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 Session
的 userPreferences
对象。因此,容器会创建一个对象,它公开与 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.
因此,在将 request
和 session
作用域的 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
还支持将 HttpServletRequest
、HttpServletResponse
、HttpSession
、WebRequest
(如果存在 JSF)FacesContext
和 ExternalContext
注入到 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 范围机制是可扩展的。您可以定义自己的范围,甚至可以重新定义现有范围,不过后者被认为是不良做法,而且您无法覆盖内置的 singleton
和 prototype
范围。
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 容器本身的此类名称示例包括 singleton
和 prototype
。registerScope(..)
方法的第二个参数是您希望注册并使用的自定义 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.
下一个示例使用 |
The next example uses |
-
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 |