Proxying Mechanisms
通过设置 <aop:config>
或 <aop:aspectj-autoproxy>
元素的 proxy-target-class
属性为 true
,可以强制使用 CGLIB 代理。然而,使用 CGLIB 代理会带来一些限制,例如无法建议最终方法,并且在 JDK 9+ 平台上存在模块系统限制。与 Spring AOP 完全耦合也是强制使用 CGLIB 代理的一种方式,但这是不可取的,违背了 AOP 原则。
Spring AOP 使用 JDK 动态代理或 CGLIB 为给定的目标对象创建代理。JDK 动态代理内置于 JDK 中,而 CGLIB 是一个常见的开源类定义库(重新打包到 spring-core
中)。
Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given
target object. JDK dynamic proxies are built into the JDK, whereas CGLIB is a common
open-source class definition library (repackaged into spring-core
).
如果要代理的目标对象至少实现了某个接口,则会使用 JDK 动态代理。将代理目标类型实现的所有接口。如果目标对象不实现任何接口,将会创建 CGLIB 代理。
If the target object to be proxied implements at least one interface, a JDK dynamic proxy is used. All of the interfaces implemented by the target type are proxied. If the target object does not implement any interfaces, a CGLIB proxy is created.
如果你想强制使用 CGLIB 代理(例如,代理为目标对象定义的每个方法,而不仅仅是其接口实现的方法),你可以这样做。但是,你应该考虑以下问题:
If you want to force the use of CGLIB proxying (for example, to proxy every method defined for the target object, not only those implemented by its interfaces), you can do so. However, you should consider the following issues:
-
With CGLIB,
final
methods cannot be advised, as they cannot be overridden in runtime-generated subclasses. -
As of Spring 4.0, the constructor of your proxied object is NOT called twice anymore, since the CGLIB proxy instance is created through Objenesis. Only if your JVM does not allow for constructor bypassing, you might see double invocations and corresponding debug log entries from Spring’s AOP support.
-
Your CGLIB proxy usage may face limitations with the JDK 9+ platform module system. As a typical case, you cannot create a CGLIB proxy for a class from the
java.lang
package when deploying on the module path. Such cases require a JVM bootstrap flag--add-opens=java.base/java.lang=ALL-UNNAMED
which is not available for modules.
要强制使用 CGLIB 代理,请将 <aop:config>
元素的 proxy-target-class
属性值设为 true,如下所示:
To force the use of CGLIB proxies, set the value of the proxy-target-class
attribute
of the <aop:config>
element to true, as follows:
<aop:config proxy-target-class="true">
<!-- other beans defined here... -->
</aop:config>
当你使用 @AspectJ 自动代理支持时,要强制使用 CGLIB 代理,请将 <aop:aspectj-autoproxy>
元素的 proxy-target-class
属性设为 true
,如下所示:
To force CGLIB proxying when you use the @AspectJ auto-proxy support, set the
proxy-target-class
attribute of the <aop:aspectj-autoproxy>
element to true
,
as follows:
<aop:aspectj-autoproxy proxy-target-class="true"/>
多个 Multiple 明确地说,在 To be clear, using |
Understanding AOP Proxies
Spring AOP 是基于代理的。在你编写自己的切面或使用 Spring Framework 提供的基于 Spring AOP 的任何切面前,完全理解最后一条语句的语义至关重要。
Spring AOP is proxy-based. It is vitally important that you grasp the semantics of what that last statement actually means before you write your own aspects or use any of the Spring AOP-based aspects supplied with the Spring Framework.
首先考虑一个场景,其中你有一个普通、未代理、没什么特别的、端正的对象引用,如下面的代码片段所示:
Consider first the scenario where you have a plain-vanilla, un-proxied, nothing-special-about-it, straight object reference, as the following code snippet shows:
-
Java
-
Kotlin
public class SimplePojo implements Pojo {
public void foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar();
}
public void bar() {
// some logic...
}
}
class SimplePojo : Pojo {
fun foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar()
}
fun bar() {
// some logic...
}
}
如果你在对象引用上调用某个方法,则会直接在该对象引用上调用该方法,如下面的图片和清单所示:
If you invoke a method on an object reference, the method is invoked directly on that object reference, as the following image and listing show:
-
Java
-
Kotlin
public class Main {
public static void main(String[] args) {
Pojo pojo = new SimplePojo();
// this is a direct method call on the 'pojo' reference
pojo.foo();
}
}
fun main() {
val pojo = SimplePojo()
// this is a direct method call on the 'pojo' reference
pojo.foo()
}
客户端代码拥有的引用是代理时,情况会略有变化。考虑下面的图表和代码片段:
Things change slightly when the reference that client code has is a proxy. Consider the following diagram and code snippet:
-
Java
-
Kotlin
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
fun main() {
val factory = ProxyFactory(SimplePojo())
factory.addInterface(Pojo::class.java)
factory.addAdvice(RetryAdvice())
val pojo = factory.proxy as Pojo
// this is a method call on the proxy!
pojo.foo()
}
这里要理解的关键是 Main
类的 main(..)
方法中的客户端代码引用代理。这意味着对该对象引用的方法调用是对代理的调用。因此,代理可以委托给与该特定方法调用相关的所有拦截器(建议)。但是,一旦调用最终到达目标对象(在本例中为 SimplePojo
引用),它对自身发出的任何方法调用,例如 this.bar()
或 this.foo()
,都将针对 this
引用调用,而不是代理。这有重要的意义。这意味着自调用不会导致与方法调用关联的建议有机会运行。
The key thing to understand here is that the client code inside the main(..)
method
of the Main
class has a reference to the proxy. This means that method calls on that
object reference are calls on the proxy. As a result, the proxy can delegate to all of
the interceptors (advice) that are relevant to that particular method call. However,
once the call has finally reached the target object (the SimplePojo
reference in
this case), any method calls that it may make on itself, such as this.bar()
or
this.foo()
, are going to be invoked against the this
reference, and not the proxy.
This has important implications. It means that self-invocation is not going to result
in the advice associated with a method invocation getting a chance to run.
好吧,那么该怎么做呢?最好的方法(这里宽泛地使用了“最好”这个词)是重构你的代码,使其不发生自调用。这确实需要你做一些工作,但这是最好、侵入性最小的方法。下一步绝对可怕,我们犹豫着指出这一点,恰恰因为它太可怕了。你可以(尽管对于我们来说这是痛苦的)将类内的逻辑完全绑定到 Spring AOP,如下面的示例所示:
Okay, so what is to be done about this? The best approach (the term "best" is used loosely here) is to refactor your code such that the self-invocation does not happen. This does entail some work on your part, but it is the best, least-invasive approach. The next approach is absolutely horrendous, and we hesitate to point it out, precisely because it is so horrendous. You can (painful as it is to us) totally tie the logic within your class to Spring AOP, as the following example shows:
-
Java
-
Kotlin
public class SimplePojo implements Pojo {
public void foo() {
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
class SimplePojo : Pojo {
fun foo() {
// this works, but... gah!
(AopContext.currentProxy() as Pojo).bar()
}
fun bar() {
// some logic...
}
}
这样做会将你的代码与 Spring AOP 完全耦合,并且使类本身意识到它正在 AOP 上下文中使用,这样做违背了 AOP 的原则。它还要求在创建代理时进行一些额外的配置,如下面的示例所示:
This totally couples your code to Spring AOP, and it makes the class itself aware of the fact that it is being used in an AOP context, which flies in the face of AOP. It also requires some additional configuration when the proxy is being created, as the following example shows:
-
Java
-
Kotlin
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
factory.setExposeProxy(true);
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
fun main() {
val factory = ProxyFactory(SimplePojo())
factory.addInterface(Pojo::class.java)
factory.addAdvice(RetryAdvice())
factory.isExposeProxy = true
val pojo = factory.proxy as Pojo
// this is a method call on the proxy!
pojo.foo()
}
最后,必须注意,AspectJ 并不存在自调用问题,因为它不是基于代理的 AOP 框架。
Finally, it must be noted that AspectJ does not have this self-invocation issue because it is not a proxy-based AOP framework.