?
Dokumen ini menggunakan Manual laman web PHP Cina Lepaskan
Spring AOP部分使用JDK動態(tài)代理或者CGLIB來為目標對象創(chuàng)建代理。(建議優(yōu)先使用JDK的動態(tài)代理)
如果被代理的目標對象實現(xiàn)了至少一個接口,則會使用JDK動態(tài)代理。所有該目標類型實現(xiàn)的接口都將被代理。 若該目標對象沒有實現(xiàn)任何接口,則創(chuàng)建一個CGLIB代理。
如果你希望強制使用CGLIB代理,(例如:希望代理目標對象的所有方法,而不只是實現(xiàn)自接口的方法) 那也可以。但是需要考慮以下問題:
無法通知(advise)Final
方法,因為他們不能被覆寫。
你需要將CGLIB 2二進制發(fā)行包放在classpath下面,與之相較JDK本身就提供了動態(tài)代理。 當需要CGLIB而在classpath下又沒有找到CGLIB類庫的話,Spring會自動提醒。
代理對象的構(gòu)造器會被調(diào)用兩次。這是很自然的結(jié)果因為在CGLIB代理模式下每一個代理對象都會 產(chǎn)生一個子類。每一個代理實例會生成兩個對象:實際代理對象和它的一個實現(xiàn)了通知的子類實例 而是用JDK代理時不會出現(xiàn)這樣的行為。通常情況下,調(diào)用代理類型的構(gòu)造器兩次并不是問題, 因為除了會發(fā)生指派外沒有任何真正的邏輯被實現(xiàn)。
強制使用CGLIB代理需要將<aop:config>
的proxy-target-class
屬性設(shè)為true:
<aop:config proxy-target-class="true"> <!-- other beans defined here... --> </aop:config>
當使用@AspectJ自動代理時要強制使用CGLIB,請將<aop:aspectj-autoproxy>
的proxy-target-class
屬性設(shè)置為true
:
<aop:aspectj-autoproxy proxy-target-class="true"/>
多個<aop:config/>
片段在運行時被包含到一個統(tǒng)一的自動代理構(gòu)造器中,
它為任何<aop:config/>
片段(一般來自不同的XML bean定義文件)中指定的內(nèi)容應(yīng)用
最強的代理設(shè)置。此設(shè)置同樣也適用于<tx:annotation-driven/>
和<aop:aspectj-autoproxy/>
元素。
清楚地講,在<tx:annotation-driven/>
、
<aop:aspectj-autoproxy/>
或者<aop:config/>
元素上使用'proxy-target-class="true"
'會導(dǎo)致將CGLIB代理應(yīng)用于此三者之上。
Spring AOP是基于代理機制的。實際上在你編寫自己的切面或者 使用任何由Spring框架提供的基于Spring AOP切面之前,深刻領(lǐng)會這一句的意思是非常重要的。
考慮如下場景,當你拿到一個無代理的、無任何特殊之處的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... } }
當你調(diào)用一個對象引用的方法時,此對象引用上的方法直接被調(diào)用,如下所示
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();
}
}
理解此處的關(guān)鍵是Main
類main(..)
方法中的客戶代碼
擁有一個代理的引用。這意味著對這個對象引用中方法的調(diào)用就是對代理的調(diào)用,
而這個代理能夠代理所有跟特定方法調(diào)用相關(guān)的攔截器。不過,一旦調(diào)用最終抵達了目標對象
(此處為SimplePojo
類的引用),任何對自身的調(diào)用例如
this.bar()
或者this.foo()
將對this
引用進行調(diào)用而非代理。這一點意義重大,
它意味著自我調(diào)用將不會導(dǎo)致和方法調(diào)用關(guān)聯(lián)的通知得到執(zhí)行的機會。
那好,為此要怎么辦呢?最好的辦法(這里使用最好這個術(shù)語不甚精確)就是重構(gòu)你的代碼使自我調(diào)用不會出現(xiàn)。 當然,這的確需要你做一些工作,但卻是最好的,最少侵入性的方法。另一個方法則很可怕, 也正因為如此我?guī)缀醪辉钢赋鲞@種方法。你可以象如下這樣完全把業(yè)務(wù)邏輯寫在你的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。當代理在被創(chuàng)建時也需要一些額外的配置:
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不存在這種自我調(diào)用的問題,因為它并不是一個基于代理的AOP框架。