Spring使用ProxyFactoryBean創(chuàng)建代理對象
概述
在Spring中創(chuàng)建AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean。這提供了對切入點、應用的任何通知及其順序的完全控制。但是,如果你不需要這種控制,則有更簡單的選項更可取。
和其他Spring FactoryBean實現(xiàn)一樣,ProxyFactoryBean引入了一個間接層。如果定義了一個名為foo的ProxyFactoryBean,那么引用foo的對象不會看到ProxyFactoryBean實例本身,而是看到一個由ProxyFactoryBean中的getObject()方法實現(xiàn)創(chuàng)建的對象。此方法創(chuàng)建封裝目標對象的AOP代理。
使用ProxyFactoryBean或另一個支持IoC的類來創(chuàng)建AOP代理的最重要的好處之一是,IoC也可以管理Advice和Pointcut。這是一個強大的特性,可以實現(xiàn)某些用其他AOP框架難以實現(xiàn)的方法。例如,通知本身可以引用應用程序?qū)ο?目標之外,它應該在任何AOP框架中可用),受益于依賴項注入提供的所有可插拔性。
ProxyFactoryBean 屬性
和Spring提供的大多數(shù)FactoryBean實現(xiàn)一樣,ProxyFactoryBean類本身就是一個JavaBean。它的屬性用于:
- 指定要代理的目標
- 指定是否使用CGLIB
一些關(guān)鍵屬性繼承自org.springframework.aop.framework.ProxyConfig (Spring中所有AOP代理工廠的超類)。這些關(guān)鍵屬性包括: - proxyTargetClass: 如果要代理的是目標類,而不是目標類的接口,則為true。如果該屬性值設(shè)置為true,則創(chuàng)建CGLIB代理。
- optimize: 控制是否對通過CGLIB創(chuàng)建的代理應用激進的優(yōu)化。除非你完全理解相關(guān)AOP代理如何處理優(yōu)化,否則不應該輕松地使用此設(shè)置。目前僅用于CGLIB代理。它對JDK動態(tài)代理沒有影響。
- frozen: 如果代理配置被凍結(jié),則不再允許更改該配置。無論是作為輕微的優(yōu)化,還是當你不希望調(diào)用者在創(chuàng)建代理后能夠操作代理,這都是有用的。此屬性的默認值為false,因此允許更改(例如添加額外的通知)。
- exposeProxy: 確定是否應該在ThreadLocal中暴露當前代理,以便目標可以訪問它。如果目標需要獲取代理,而exposeProxy屬性被設(shè)置為true,那么可以使用AopContext.currentProxy()方法。
ProxyFactoryBean特有的其他屬性包括:
- proxyInterfaces: 接口名稱的字符串數(shù)組。如果沒有提供,則使用目標類的CGLIB代理
- interceptorNames: 建議器、攔截器或其他要應用的建議器名稱的字符串數(shù)組。 這些名稱是當前工廠中的bean名稱,包括來自祖先工廠的bean名稱。這里不能提到bean引用,因為這樣做會導致ProxyFactoryBean忽略通知的單例設(shè)置。
你可以在攔截器名稱后面加上星號(*)。這樣做的結(jié)果是,所有advisor bean的名稱都以要應用的星號之前的部分開頭。你可以在使用“全局”建議器中找到使用此功能的例子。 - singleton: 不管getObject()方法被調(diào)用的頻率如何,工廠是否應該返回一個對象。有幾個FactoryBean實現(xiàn)提供了這樣的方法。默認值為true。
基于JDK和CGLIB的代理
?ProxyFactoryBean如何選擇為特定目標對象(要代理的對象)創(chuàng)建基于JDK的代理或基于CGLIB的代理的最終文檔。
如果要代理的目標對象的類(以下簡稱為目標類)沒有實現(xiàn)任何接口,則創(chuàng)建一個基于cglib的代理。這是最簡單的場景,因為JDK代理是基于接口的,沒有接口意味著JDK代理根本不可能。你可以插入目標bean并通過設(shè)置interceptorNames屬性來指定攔截器列表。注意,即使將ProxyFactoryBean的proxyTargetClass屬性設(shè)置為false,也會創(chuàng)建一個基于cglib的代理。
如果目標類實現(xiàn)了一個(或多個)接口,則創(chuàng)建的代理類型取決于ProxyFactoryBean的配置。
如果ProxyFactoryBean的proxyTargetClass屬性被設(shè)置為true,則會創(chuàng)建一個基于cglib的代理。這是有道理的,也符合最小意外原則。即使ProxyFactoryBean的proxyInterfaces屬性被設(shè)置為一個或多個完全限定的接口名,proxyTargetClass屬性被設(shè)置為true這一事實也會導致基于cglib的代理生效。
如果將ProxyFactoryBean的proxyInterfaces屬性設(shè)置為一個或多個完全限定的接口名,則創(chuàng)建一個基于jdk的代理。創(chuàng)建的代理實現(xiàn)了在proxyInterfaces屬性中指定的所有接口。如果目標類碰巧實現(xiàn)了比proxyInterfaces屬性中指定的更多的接口,那也沒什么問題,但返回的代理不會實現(xiàn)這些額外的接口。
如果ProxyFactoryBean的proxyInterfaces屬性沒有被設(shè)置,但是目標類確實實現(xiàn)了一個(或多個)接口,那么ProxyFactoryBean會自動檢測到目標類實際上至少實現(xiàn)了一個接口,并且創(chuàng)建了一個基于jdk的代理。實際被代理的接口是目標類實現(xiàn)的所有接口。實際上,這相當于為目標類的proxyInterfaces屬性提供一個列表,其中包含目標類實現(xiàn)的所有接口。然而,這樣做工作量大大減少,也不容易出現(xiàn)排版錯誤。
代理接口
考慮一個使用ProxyFactoryBean的簡單例子。這個例子涉及:
- 考慮一個使用ProxyFactoryBean的簡單例子。這個例子涉及:
- 顧問(Advisor)和攔截器(Interceptor)用來提供通知。
- 一個AOP代理bean定義,用來指定目標對象(personTarget bean)、代理的接口和要應用的建議。
注意,interceptorNames屬性接受一個字符串列表,其中包含當前工廠中攔截器或顧問的bean名稱。你可以使用顧問,攔截器,返回之前,之后,并拋出建議對象。
前面給出的person bean定義可以代替person實現(xiàn),如下所示:
同一個IoC上下文中的其他bean可以表達對它的強類型依賴,就像普通Java對象一樣。如下面的例子所示:
這個例子中的PersonUser類公開了一個Person類型的屬性。就它所關(guān)心的而言,AOP代理可以透明地代替“真實的”人實現(xiàn)。但是,它的類將是一個動態(tài)代理類。
代理類
如果需要代理一個類,而不是一個或多個接口,該怎么辦?
想象一下,在我們前面的例子中,沒有Person接口。我們需要建議一個名為Person的類,它沒有實現(xiàn)任何業(yè)務(wù)接口。在這種情況下,你可以配置Spring使用CGLIB代理,而不是動態(tài)代理。為此,將前面給出的ProxyFactoryBean的proxyTargetClass屬性設(shè)置為true。雖然最好是針對接口而不是類進行編程,但在處理遺留代碼時,能夠建議不實現(xiàn)接口的類可能很有用。(一般來說,Spring不是規(guī)定性的。雖然它使應用良好的實踐變得容易,但它避免了強制使用特定的方法。)
如果你愿意,你可以在任何情況下強制使用CGLIB,即使你有接口。
CGLIB代理通過在運行時生成目標類的子類來工作。Spring將這個生成的子類配置為將方法調(diào)用委托給原始目標。子類用于實現(xiàn)裝飾器模式,在建議中編織。
CGLIB代理一般應該對用戶透明。但是,有一些問題需要考慮:
- Final方法無法提供建議,因為它們不能被覆蓋
- 不需要將CGLIB添加到類路徑中。從Spring 3.2開始,CGLIB被重新打包并包含在Spring -core JAR中。換句話說,基于cglib的AOP可以“開箱即用”,JDK的動態(tài)代理也是如此
使用“全局”顧問(Advisor)
通過在攔截器名稱后面附加一個星號,所有bean名稱與星號之前的部分匹配的顧問都被添加到顧問鏈中。如果你需要添加一組標準的“全局”顧問,這可以派上用場。下面的例子定義了兩個全局advisor: