This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Framework 6.2.1! |
Proxying Mechanisms
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
).
If the target object to be proxied implements at least one interface, a JDK dynamic proxy is used, and 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 which is a runtime-generated subclass of the target type.
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:
-
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 – for example, package-private methods in a parent class from a different package – cannot be advised because they are effectively private.
-
The constructor of your proxied object will not be called twice, since the CGLIB proxy instance is created through Objenesis. However, 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 Java 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.
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>
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 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 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()
}
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. In other words,
self invocation via an explicit or implicit this
reference will bypass the advice.
To address that, you have the following options.
- Avoid self invocation
-
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.
- Inject a self reference
-
An alternative approach is to make use of self injection, and invoke methods on the proxy via the self reference instead of via
this
. - Use
AopContext.currentProxy()
-
This last approach is highly discouraged, and we hesitate to point it out, in favor of the previous options. However, as a last resort you can choose to 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 it should be avoided if possible.
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
class SimplePojo : Pojo {
fun foo() {
// This works, but it should be avoided if possible.
(AopContext.currentProxy() as Pojo).bar()
}
fun bar() {
// some logic...
}
}
The use of AopContext.currentProxy()
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
reduces some of the benefits of AOP. It also requires that the ProxyFactory
is
configured to expose the proxy, 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 compile-time weaving and load-time weaving do not have this self-invocation issue because they apply advice within the bytecode instead of via a proxy. |