Spring AOP部分使用JDK动态代理或者CGLIB来为目标对象创建代理。(建议优先使用JDK的动态代理)
如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。所有该目标类型实现的接口都将被代理。 若该目标对象没有实现任何接口,则创建一个CGLIB代理。
如果你希望强制使用CGLIB代理,(例如:希望代理目标对象的所有方法,而不只是实现自接口的方法) 那也可以。但是需要考虑以下问题:
无法通知(advise)
Final
方法,因为他们不能被覆写。你需要将CGLIB 2二进制发行包放在classpath下面,与之相较JDK本身就提供了动态代理。 当需要CGLIB而在classpath下又没有找到CGLIB类库的话,Spring会自动提醒。
代理对象的构造器会被调用两次。这是很自然的结果因为在CGLIB代理模式下每一个代理对象都会 产生一个子类。每一个代理实例会生成两个对象:实际代理对象和它的一个实现了通知的子类实例 而是用JDK代理时不会出现这样的行为。通常情况下,调用代理类型的构造器两次并不是问题, 因为除了会发生指派外没有任何真正的逻辑被实现。
强制使用CGLIB代理需要将<aop:config>
的proxy-target-class
属性设为true:
<aop:config proxy-target-class="true"> <!-- other beans defined here... --> </aop:config>
当使用@AspectJ自动代理时要强制使用CGLIB,请将<aop:aspectj-autoproxy>
的proxy-target-class
属性设置为true
:
<aop:aspectj-autoproxy proxy-target-class="true"/>
注意
多个<aop:config/>
片段在运行时被包含到一个统一的自动代理构造器中,
它为任何<aop:config/>
片段(一般来自不同的XML bean定义文件)中指定的内容应用
最强的代理设置。此设置同样也适用于<tx:annotation-driven/>
和<aop:aspectj-autoproxy/>
元素。
清楚地讲,在<tx:annotation-driven/>
、
<aop:aspectj-autoproxy/>
或者<aop:config/>
元素上使用'proxy-target-class="true"
'会导致将CGLIB代理应用于此三者之上。
Spring AOP是基于代理机制的。实际上在你编写自己的切面或者 使用任何由Spring框架提供的基于Spring AOP切面之前,深刻领会这一句的意思是非常重要的。
考虑如下场景,当你拿到一个无代理的、无任何特殊之处的POJO对象引用时,如以下代码段所示
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... } }
当你调用一个对象引用的方法时,此对象引用上的方法直接被调用,如下所示
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();
}
}
当客户代码所持有的引用是一个代理的时候则略有不同了。请考虑如下图示和代码段片断
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();
}
}
理解此处的关键是Main
类main(..)
方法中的客户代码
拥有一个代理的引用。这意味着对这个对象引用中方法的调用就是对代理的调用,
而这个代理能够代理所有跟特定方法调用相关的拦截器。不过,一旦调用最终抵达了目标对象
(此处为SimplePojo
类的引用),任何对自身的调用例如
this.bar()
或者this.foo()
将对this
引用进行调用而非代理。这一点意义重大,
它意味着自我调用将不会导致和方法调用关联的通知得到执行的机会。
那好,为此要怎么办呢?最好的办法(这里使用最好这个术语不甚精确)就是重构你的代码使自我调用不会出现。 当然,这的确需要你做一些工作,但却是最好的,最少侵入性的方法。另一个方法则很可怕, 也正因为如此我几乎不愿指出这种方法。你可以象如下这样完全把业务逻辑写在你的Spring AOP类中:
public class SimplePojo implements Pojo { public void foo() { // this works, but... gah! ((Pojo) AopContext.currentProxy()).bar(); } public void bar() { // some logic... } }
这样完全将你的代码交给了Spring AOP,并且让类本身知道它正被用于一个AOP的上下文中, 而它其中的文件直接面对AOP。当代理在被创建时也需要一些额外的配置:
public class Main { public static void main(String[] args) { ProxyFactory factory = new ProxyFactory(new SimplePojo()); factory.adddInterface(Pojo.class); factory.addAdvice(new RetryAdvice()); factory.setExposeProxy(true); Pojo pojo = (Pojo) factory.getProxy(); // this is a method call on the proxy! pojo.foo(); } }
最后,必须注意AspectJ不存在这种自我调用的问题,因为它并不是一个基于代理的AOP框架。