Scoped Beans as Dependencies
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.
springioc容器不仅管理对象(bean)的实例化,还管理协作者(或依赖项)的连接。如果您想(例如)将一个HTTP请求作用域的bean注入到另一个具有较长生存期的bean中,那么您可以选择注入一个AOP代理来代替作用域bean。也就是说,您需要注入一个代理对象,该对象公开与作用域对象相同的公共接口,但它也可以从相关的作用域(如一个HTTP请求)检索真实的目标对象,并将方法调用委托给实际对象。
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.
您也可以使用<aop:scoped-proxy/>
在作用域为singleton
的bean之间,引用通过可序列化的中间代理,因此能够在反序列化
时重新获取目标singleton
bean。
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.
申报<aop:scoped-proxy/>
时对于作用域为prototype
的bean,对共享代理的每个方法调用都会导致创建一个新的目标实例,然后将调用转发到该实例。
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.
此外,作用域代理并不是以生命周期安全的方式从较短范围访问bean的唯一方法。您还可以将注入点(即构造函数或setter参数或autowired
字段)声明为ObjectFactory<MyTargetBean>
,允许每次需要时使用getObject()
调用来检索当前实例,而无需保留实例或单独存储它。
As an extended variant, you may declare ObjectProvider<MyTargetBean>
, which delivers several additional access variants, including getIfAvailable
and getIfUnique
.
作为一个扩展变量,您可以声明ObjectProvider<MyTargetBean>
,它提供了几个附加的访问变量,包括getIfAvailable
和getIfUnique
。
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.
它的JSR-330变体称为Provider
,并与Provider<MyTargetBean>
声明和每次检索尝试对应的get()
调用一起使用。关于JSR-330的更多细节,请参阅这里。
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. |
---|---|
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). Why do definitions of beans scoped at the request
, session
and custom-scope levels require the <aop:scoped-proxy/>
element? 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):
要创建这样的代理,需要插入一个子代 <aop:scoped-proxy/>
元素放入作用域bean定义中(请参阅选择要创建的代理类型和基于XML模式的配置)。为什么在 request
, session
和自定义范围级别定义bean需要 <aop:scoped-proxy/>
元素?考虑下面的singleton bean定义,并将其与您需要为上述范围定义的内容进行对比(请注意,下面的userPreferences
bean定义是不完整的):
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
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(userManager
)被注入一个对HTTP会话作用域bean(userPreferences
)的引用。这里突出的一点是,usermanager
bean是一个单例:它在每个容器中只实例化一次,它的依赖项(在本例中只有一个,userPreferences
bean)也只被注入一次。这意味着usermanager
bean只对完全相同的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.
将作用域较短的bean注入到一个作用域较长的bean中是不合理的(例如,将HTTP会话范围内的协作bean作为依赖项注入到singleton bean中)。相反,您需要一个userManager
对象,并且处于HTTP会话的生存期,您需要一个特定于HTTP会话的userPreferences
对象。因此,容器创建一个对象,该对象公开与UserPreferences
类完全相同的公共接口(理想情况下是UserPreferences
实例的对象),该对象可以从作用域机制(HTTP请求、会话等)获取真正的UserPreferences
对象。容器将这个代理对象注入到usermanager
bean中,它不知道这个UserPreferences
引用是一个代理。在本例中,当UserManager
实例调用依赖注入的UserPreferences
对象上的方法时,它实际上是在调用代理上的方法。然后代理从HTTP会话(在本例中)获取真实的UserPreferences
对象,并将方法调用委托给检索到的真的UserPreferences
对象。
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时,需要以下配置(正确且完整),如下例所示:
<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
选择要创建的代理类型
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.
默认情况下,当Spring容器为标记为<aop:scoped-proxy/>
元素,则创建基于CGLIB的类代理。
CGLIB proxies intercept only public method calls! Do not call non-public methods on such a proxy. They are not delegated to the actual scoped target object.
CGLIB代理只拦截公共方法调用!不要对此类代理调用非公共方法。它们不会委托给实际作用域的目标对象。
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:
或者,您可以配置Spring容器,为此类作用域bean创建基于JDK接口
的标准代理,proxy-target-class
属性的值指定为false<aop:scoped-proxy/>
元素。使用基于JDK接口的代理意味着您不需要在应用程序类路径中添加其他库来影响这种代理。但是,这也意味着作用域bean的类必须实现至少一个接口,并且将作用域bean注入其中的所有协作者都必须通过它的一个接口引用该bean。以下示例显示基于接口的代理:
<!-- 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>
For more detailed information about choosing class-based or interface-based proxying, see Proxying Mechanisms.
有关选择基于类或基于接口的代理的详细信息,请参阅代理机制。