Using the ProxyFactoryBean to Create AOP Proxies

如果您将 Spring IoC 容器(ApplicationContextBeanFactory)用于您的业务对象(您应该这么做!),您应该使用 Spring 的 AOP FactoryBean 实现之一。(记住,一个工厂 Bean 引入了间接层,允许其创建不同类型的对象。)

If you use the Spring IoC container (an ApplicationContext or BeanFactory) for your business objects (and you should be!), you want to use one of Spring’s AOP FactoryBean implementations. (Remember that a factory bean introduces a layer of indirection, letting it create objects of a different type.)

Spring AOP 支持也在后台使用工厂 bean。

The Spring AOP support also uses factory beans under the covers.

在 Spring 中创建 AOP 代理的基本方法是使用 org.springframework.aop.framework.ProxyFactoryBean。这给予了对切点、任何适用的建议及它们的顺序的完全控制权。然而,如果您不需要如此控制权,还有一些更简单的备选方法值得考虑。

The basic way to create an AOP proxy in Spring is to use the org.springframework.aop.framework.ProxyFactoryBean. This gives complete control over the pointcuts, any advice that applies, and their ordering. However, there are simpler options that are preferable if you do not need such control.

Basics

ProxyFactoryBean,就像其他 Spring FactoryBean 实现一样,引入了间接层。如果您定义了一个名为 fooProxyFactoryBean,则引用 foo 的对象不会看到 ProxyFactoryBean 实例本身,而是看到由 ProxyFactoryBean 中的 getObject() 方法实现创建的对象。此方法创建一个包装了目标对象的一个 AOP 代理。

The ProxyFactoryBean, like other Spring FactoryBean implementations, introduces a level of indirection. If you define a ProxyFactoryBean named foo, objects that reference foo do not see the ProxyFactoryBean instance itself but an object created by the implementation of the getObject() method in the ProxyFactoryBean . This method creates an AOP proxy that wraps a target object.

使用 ProxyFactoryBean 或另一个 IoC 感知类来创建 AOP 代理的最重要的优点之一是建议和切点也可以由 IoC 管理。这是一个强大的功能,使得通过其他 AOP 框架难以实现的某些方法成为可能。例如,建议本身可以引用应用程序对象(除了目标,目标在任何 AOP 框架中都应该可用),还可以利用依赖注入所提供的全部可连接性。

One of the most important benefits of using a ProxyFactoryBean or another IoC-aware class to create AOP proxies is that advice and pointcuts can also be managed by IoC. This is a powerful feature, enabling certain approaches that are hard to achieve with other AOP frameworks. For example, an advice may itself reference application objects (besides the target, which should be available in any AOP framework), benefiting from all the pluggability provided by Dependency Injection.

JavaBean Properties

与 Spring 提供的大多数 FactoryBean 实现一样,ProxyFactoryBean 类本身就是一个 JavaBean。它的属性用于:

In common with most FactoryBean implementations provided with Spring, the ProxyFactoryBean class is itself a JavaBean. Its properties are used to:

一些关键属性是从 org.springframework.aop.framework.ProxyConfig(Spring 中所有 AOP 代理工厂的超类)继承的。这些关键属性包括:

Some key properties are inherited from org.springframework.aop.framework.ProxyConfig (the superclass for all AOP proxy factories in Spring). These key properties include the following:

  • proxyTargetClass: true if the target class is to be proxied, rather than the target class’s interfaces. If this property value is set to true, then CGLIB proxies are created (but see also JDK- and CGLIB-based proxies).

  • optimize: Controls whether or not aggressive optimizations are applied to proxies created through CGLIB. You should not blithely use this setting unless you fully understand how the relevant AOP proxy handles optimization. This is currently used only for CGLIB proxies. It has no effect with JDK dynamic proxies.

  • frozen: If a proxy configuration is frozen, changes to the configuration are no longer allowed. This is useful both as a slight optimization and for those cases when you do not want callers to be able to manipulate the proxy (through the Advised interface) after the proxy has been created. The default value of this property is false, so changes (such as adding additional advice) are allowed.

  • exposeProxy: Determines whether or not the current proxy should be exposed in a ThreadLocal so that it can be accessed by the target. If a target needs to obtain the proxy and the exposeProxy property is set to true, the target can use the AopContext.currentProxy() method.

专门属于 ProxyFactoryBean 的其他属性包括:

Other properties specific to ProxyFactoryBean include the following:

  • proxyInterfaces: An array of String interface names. If this is not supplied, a CGLIB proxy for the target class is used (but see also JDK- and CGLIB-based proxies).

  • interceptorNames: A String array of Advisor, interceptor, or other advice names to apply. Ordering is significant, on a first come-first served basis. That is to say that the first interceptor in the list is the first to be able to intercept the invocation.[.iokays-translated-edbf05d684ec235cc264ce479511c805] 名称是当前工厂中的 Bean 名称,包括祖先工厂的 Bean 名称。您无法在此处提及 Bean 引用,因为这样做会导致 ProxyFactoryBean 忽略建议的单例设置。

The names are bean names in the current factory, including bean names from ancestor factories. You cannot mention bean references here, since doing so results in the ProxyFactoryBean ignoring the singleton setting of the advice.

您可以追加一个带有星号 (*) 的拦截器名称。这样做会将名称以星号前部分开头的所有 advisors bean 应用到要应用的 advisors bean。您可以在 Using “Global” Advisors中找到使用此功能的示例。

You can append an interceptor name with an asterisk (*). Doing so results in the application of all advisor beans with names that start with the part before the asterisk to be applied. You can find an example of using this feature in Using “Global” Advisors. * singleton: Whether or not the factory should return a single object, no matter how often the getObject() method is called. Several FactoryBean implementations offer such a method. The default value is true. If you want to use stateful advice - for example, for stateful mixins - use prototype advice along with a singleton value of false.

JDK- and CGLIB-based proxies

本节用作有关 ProxyFactoryBean 如何选择为某个目标对象(要被代理)创建基于 JDK 的代理或基于 CGLIB 的代理的权威文档。

This section serves as the definitive documentation on how the ProxyFactoryBean chooses to create either a JDK-based proxy or a CGLIB-based proxy for a particular target object (which is to be proxied).

ProxyFactoryBean 从 1.2.x 和 2.0 版本之间修改了创建基于 JDK 或 CGLIB 代理的行为。ProxyFactoryBean 现在表现出与 TransactionProxyFactoryBean 类的类似语义,自动检测接口。

The behavior of the ProxyFactoryBean with regard to creating JDK- or CGLIB-based proxies changed between versions 1.2.x and 2.0 of Spring. The ProxyFactoryBean now exhibits similar semantics with regard to auto-detecting interfaces as those of the TransactionProxyFactoryBean class.

如果要被代理的目标对象(以下简称目标类)的类未实现任何接口,则会创建一个基于 CGLIB 的代理。这是最简单的场景,因为基于 JDK 的代理是基于接口的,而没有接口则意味着基于 JDK 的代理甚至无法进行。您可以插入目标 Bean 并通过设置 interceptorNames 属性指定拦截器列表。请注意,即使 ProxyFactoryBeanproxyTargetClass 属性已设置为“false”,也会创建一个基于 CGLIB 的代理。(这样做毫无意义,最好从 Bean 定义中删除,因为它充其量是多余的,最坏的情况会造成混乱。)

If the class of a target object that is to be proxied (hereafter simply referred to as the target class) does not implement any interfaces, a CGLIB-based proxy is created. This is the easiest scenario, because JDK proxies are interface-based, and no interfaces means JDK proxying is not even possible. You can plug in the target bean and specify the list of interceptors by setting the interceptorNames property. Note that a CGLIB-based proxy is created even if the proxyTargetClass property of the ProxyFactoryBean has been set to false. (Doing so makes no sense and is best removed from the bean definition, because it is, at best, redundant, and, at worst confusing.)

如果目标类实现了(一个或更多个)接口,则创建的代理类型取决于 ProxyFactoryBean 的配置。

If the target class implements one (or more) interfaces, the type of proxy that is created depends on the configuration of the ProxyFactoryBean.

如果 ProxyFactoryBeanproxyTargetClass 属性已设置为 true,则会创建一个基于 CGLIB 的代理。这是有道理的,也符合最少惊讶原则。即使 ProxyFactoryBeanproxyInterfaces 属性已设置为一个或多个完全限定的接口名称,但由于 proxyTargetClass 属性已设置为“true”,会导致基于 CGLIB 的代理生效。

If the proxyTargetClass property of the ProxyFactoryBean has been set to true, a CGLIB-based proxy is created. This makes sense and is in keeping with the principle of least surprise. Even if the proxyInterfaces property of the ProxyFactoryBean has been set to one or more fully qualified interface names, the fact that the proxyTargetClass property is set to true causes CGLIB-based proxying to be in effect.

如果 ProxyFactoryBeanproxyInterfaces 属性已设置为一个或多个完全限定的接口名称,则会创建一个基于 JDK 的代理。创建的代理实现了在 proxyInterfaces 属性中指定的所有接口。如果目标类恰好实现了比 proxyInterfaces 属性中指定的目标类额外多的接口,那很好,但返回的代理不会实现这些其他接口。

If the proxyInterfaces property of the ProxyFactoryBean has been set to one or more fully qualified interface names, a JDK-based proxy is created. The created proxy implements all of the interfaces that were specified in the proxyInterfaces property. If the target class happens to implement a whole lot more interfaces than those specified in the proxyInterfaces property, that is all well and good, but those additional interfaces are not implemented by the returned proxy.

如果尚未设置 ProxyFactoryBeanproxyInterfaces 属性,但目标类确实实现了某个(或多个)接口,则 ProxyFactoryBean 会自动检测到目标类实际实现了一个以上接口,并且会创建一个基于 JDK 的代理。实际代理的接口是目标类实现的所有接口。实际上,这与向 proxyInterfaces 属性提供目标类实现的每个接口的列表相同。不过,其工作量小得多,也不太容易发生印刷错误。

If the proxyInterfaces property of the ProxyFactoryBean has not been set, but the target class does implement one (or more) interfaces, the ProxyFactoryBean auto-detects the fact that the target class does actually implement at least one interface, and a JDK-based proxy is created. The interfaces that are actually proxied are all of the interfaces that the target class implements. In effect, this is the same as supplying a list of each and every interface that the target class implements to the proxyInterfaces property. However, it is significantly less work and less prone to typographical errors.

Proxying Interfaces

思考一个 ProxyFactoryBean 工作的简单示例。此示例包含:

Consider a simple example of ProxyFactoryBean in action. This example involves:

  • A target bean that is proxied. This is the personTarget bean definition in the example.

  • An Advisor and an Interceptor used to provide advice.

  • An AOP proxy bean definition to specify the target object (the personTarget bean), the interfaces to proxy, and the advice to apply.

以下清单显示了该示例:

The following listing shows the example:

<bean id="personTarget" class="com.mycompany.PersonImpl">
	<property name="name" value="Tony"/>
	<property name="age" value="51"/>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
	<property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>

<bean id="person"
	class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="proxyInterfaces" value="com.mycompany.Person"/>

	<property name="target" ref="personTarget"/>
	<property name="interceptorNames">
		<list>
			<value>myAdvisor</value>
			<value>debugInterceptor</value>
		</list>
	</property>
</bean>

请注意,interceptorNames 属性会采用一个 String 列表,其中包含当前工厂中拦截器的 bean 名称或建议。你可以使用建议、拦截器,以及返回前、返回后和抛出建议对象。建议的顺序很重要。

Note that the interceptorNames property takes a list of String, which holds the bean names of the interceptors or advisors in the current factory. You can use advisors, interceptors, before, after returning, and throws advice objects. The ordering of advisors is significant.

你可能在想为什么列表不保留 bean 引用。原因是,如果 ProxyFactoryBean 的 single 值属性设置为 false,它必须能够返回独立代理实例。如果任何一个顾问本身是一个原型,则需要返回一个独立实例,因此有必要能够从工厂获取原型实例。保存引用是不够的。

You might be wondering why the list does not hold bean references. The reason for this is that, if the singleton property of the ProxyFactoryBean is set to false, it must be able to return independent proxy instances. If any of the advisors is itself a prototype, an independent instance would need to be returned, so it is necessary to be able to obtain an instance of the prototype from the factory. Holding a reference is not sufficient.

前面显示的 person bean 定义可以代替 Person 实现使用,如下所示:

The person bean definition shown earlier can be used in place of a Person implementation, as follows:

  • Java

  • Kotlin

Person person = (Person) factory.getBean("person");
val person = factory.getBean("person") as Person

相同 IoC 上下文中的其他 bean 可以对其表示强类型依赖,就如同普通 Java 对象一样。以下示例显示了如何执行此操作:

Other beans in the same IoC context can express a strongly typed dependency on it, as with an ordinary Java object. The following example shows how to do so:

<bean id="personUser" class="com.mycompany.PersonUser">
	<property name="person"><ref bean="person"/></property>
</bean>

此示例中的 PersonUser 类公开了类型为 Person 的属性。就它而言,AOP 代理可以透明地用于代替“真实”人类实现。不过,其类将是动态代理类。可以将其强制转换为 Advised 接口(稍后讨论)。

The PersonUser class in this example exposes a property of type Person. As far as it is concerned, the AOP proxy can be used transparently in place of a “real” person implementation. However, its class would be a dynamic proxy class. It would be possible to cast it to the Advised interface (discussed later).

你可以通过使用匿名内部 bean 来隐藏目标和代理之间的区别。只有 ProxyFactoryBean 定义有所不同。仅为完整性起见,才包含了建议。以下示例显示了如何使用匿名内部 bean:

You can conceal the distinction between target and proxy by using an anonymous inner bean. Only the ProxyFactoryBean definition is different. The advice is included only for completeness. The following example shows how to use an anonymous inner bean:

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
	<property name="someProperty" value="Custom string property value"/>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>

<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="proxyInterfaces" value="com.mycompany.Person"/>
	<!-- Use inner bean, not local reference to target -->
	<property name="target">
		<bean class="com.mycompany.PersonImpl">
			<property name="name" value="Tony"/>
			<property name="age" value="51"/>
		</bean>
	</property>
	<property name="interceptorNames">
		<list>
			<value>myAdvisor</value>
			<value>debugInterceptor</value>
		</list>
	</property>
</bean>

使用匿名内部 bean 的优点在于只有一类 Person 对象。如果我们希望防止应用程序上下文的使用者获取对未建议对象的引用,或者需要避免与 Spring IoC 自动装配存在任何歧义,则这会很有用。可以说还有个优点,即 ProxyFactoryBean 定义是自包含的。不过,有时能够从工厂获取未建议的目标实际上是一种优势(例如,在某些测试场景中)。

Using an anonymous inner bean has the advantage that there is only one object of type Person. This is useful if we want to prevent users of the application context from obtaining a reference to the un-advised object or need to avoid any ambiguity with Spring IoC autowiring. There is also, arguably, an advantage in that the ProxyFactoryBean definition is self-contained. However, there are times when being able to obtain the un-advised target from the factory might actually be an advantage (for example, in certain test scenarios).

Proxying Classes

如果你需要代理的是类(而不是一个或多个接口),该怎么办?

What if you need to proxy a class, rather than one or more interfaces?

想象在我们的早期示例中,没有 Person 接口。我们需要建议一个未实现任何业务接口的,名为 Person 的类。在这种情况下,你可以将 Spring 配置为使用 CGLIB 代理,而不是动态代理。要执行此操作,请将前面所示 ProxyFactoryBean 上的 proxyTargetClass 属性设置为 true。尽管最好对接口而非类进行编程,但当处理遗留代码时,建议不实现接口的类的功能非常有用。(总的来说,Spring 并不是规定性的。虽然它使得应用良好实践变得容易,但它避免强制采用特定方法。)

Imagine that in our earlier example, there was no Person interface. We needed to advise a class called Person that did not implement any business interface. In this case, you can configure Spring to use CGLIB proxying rather than dynamic proxies. To do so, set the proxyTargetClass property on the ProxyFactoryBean shown earlier to true. While it is best to program to interfaces rather than classes, the ability to advise classes that do not implement interfaces can be useful when working with legacy code. (In general, Spring is not prescriptive. While it makes it easy to apply good practices, it avoids forcing a particular approach.)

如果你希望的话,可以在任何情况下强制使用 CGLIB,即使你确实有接口。

If you want to, you can force the use of CGLIB in any case, even if you do have interfaces.

CGLIB 代理通过在运行时生成目标类的子类来工作。Spring 将此生成的子类配置为可将方法调用委托给原始目标。子类用于实现装饰器模式,实现建议。

CGLIB proxying works by generating a subclass of the target class at runtime. Spring configures this generated subclass to delegate method calls to the original target. The subclass is used to implement the Decorator pattern, weaving in the advice.

通常,CGLIB 代理对于用户来说应该是透明的。不过,需要考虑以下问题:

CGLIB proxying should generally be transparent to users. However, there are some issues to consider:

  • final classes cannot be proxied, because they cannot be extended.

  • final methods cannot be advised, because they cannot be overridden.

  • private methods cannot be advised, because they cannot be overridden.

  • Methods that are not visible, typically package private methods in a parent class from a different package, cannot be advised because they are effectively private.

无需将 CGLIB 添加到你的类路径。CGLIB 已重新打包并包含在 spring-core JAR 中。换句话说,基于 CGLIB 的 AOP 的开箱即用状态与 JDK 动态代理相同。

There is no need to add CGLIB to your classpath. CGLIB is repackaged and included in the spring-core JAR. In other words, CGLIB-based AOP works "out of the box", as do JDK dynamic proxies.

CGLIB 代理和动态代理之间的性能差异很小。在此情况下,性能不应该是决定性因素。

There is little performance difference between CGLIB proxies and dynamic proxies. Performance should not be a decisive consideration in this case.

Using “Global” Advisors

通过向拦截器名称追加星号,所有与星号前部分匹配的 bean 名称的建议都会添加到建议链。如果你需要添加一组标准“全局”建议,这会派上用场。以下示例定义了两个全局建议:

By appending an asterisk to an interceptor name, all advisors with bean names that match the part before the asterisk are added to the advisor chain. This can come in handy if you need to add a standard set of “global” advisors. The following example defines two global advisors:

<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
	<property name="target" ref="service"/>
	<property name="interceptorNames">
		<list>
			<value>global*</value>
		</list>
	</property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>