?
Ce document utilise Manuel du site Web PHP chinois Libérer
如果你正在使用Spring IoC容器(即ApplicationContext或BeanFactory)來管理你的業(yè)務對象--這正是你應該做的--你也許會想要使用Spring中關(guān)于AOP的FactoryBean。 (記住使用工廠bean引入一個間接層之后,我們就可以創(chuàng)建不同類型的對象了)。
Spring 2.0的AOP支持也在底層使用工廠bean。
在Spring里創(chuàng)建一個AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean。 這個類對應用的切入點和通知提供了完整的控制能力(包括它們的應用順序)。然而如果你不需要這種控制,你會喜歡更簡單的方式。
像其它的FactoryBean
實現(xiàn)一樣,ProxyFactoryBean
引入了一個間接層。如果你定義一個名為foo
的ProxyFactoryBean
,
引用foo
的對象看到的將不是ProxyFactoryBean
實例本身,而是一個ProxyFactoryBean
實現(xiàn)里getObject()
方法所創(chuàng)建的對象。
這個方法將創(chuàng)建一個AOP代理,它包裝了一個目標對象。
使用ProxyFactoryBean
或者其它IoC相關(guān)類帶來的最重要的好處之一就是創(chuàng)建AOP代理,這意味著通知和切入點也可以由IoC來管理。這是一個強大的功能并使得某些特定的解決方案成為可能,
而這些用其它AOP框架很難做到。例如,一個通知也許本身也要引用應用程序?qū)ο螅ú粌H僅是其它AOP框架中也可以訪問的目標對象),這令你可以從依賴注射的可拔插特性中獲益。
通常情況下Spring提供了大多數(shù)的FactoryBean
實現(xiàn),ProxyFactoryBean
類本身也是一個JavaBean。它的屬性被用來:
指定你希望代理的目標對象
指定是否使用CGLIB(查看下面叫做第?7.5.3?節(jié) “基于JDK和CGLIB的代理”的小節(jié))。
一些主要屬性從org.springframework.aop.framework.ProxyConfig
里繼承下來(這個類是Spring里所有AOP代理工廠的父類)。這些主要屬性包括:
proxyTargetClass
:這個屬性為true
時,目標類本身被代理而不是目標類的接口。如果這個屬性值被設(shè)為true
,CGLIB代理將被創(chuàng)建(可以參看下面名為第?7.5.3?節(jié) “基于JDK和CGLIB的代理”的章節(jié))。
optimize
:用來控制通過CGLIB創(chuàng)建的代理是否使用激進的優(yōu)化策略。
除非完全了解AOP代理如何處理優(yōu)化,否則不推薦用戶使用這個設(shè)置。目前這個屬性僅用于CGLIB代理;
對于JDK動態(tài)代理(缺省代理)無效。
frozen
:如果一個代理配置是frozen
的,就不允許對該配置進行修改。
這在簡單優(yōu)化和不希望調(diào)用者在代理創(chuàng)建后操作代理(通過Advised
接口)
時很有用。缺省值為false
,即可以進行類似添加附加通知的操作。
exposeProxy
:決定當前代理是否被暴露在一個ThreadLocal
中以便被目標對象訪問。如果目標對象需要獲取代理而且exposeProxy
屬性被設(shè)為
true
,目標對象可以使用AopContext.currentProxy()
方法。
aopProxyFactory
:使用AopProxyFactory
的實現(xiàn)。這提供了一種方法來自定義是否使用動態(tài)代理,CGLIB或其它代理策略。
缺省實現(xiàn)將根據(jù)情況選擇動態(tài)代理或者CGLIB。一般情況下應該沒有使用這個屬性的需要;它是被設(shè)計來在Spring 1.1中添加新的代理類型的。
ProxyFactoryBean
中需要說明的其它屬性包括:
proxyInterfaces
:需要代理的接口名的字符串數(shù)組。
如果沒有提供,將為目標類使用一個CGLIB代理(也可以查看下面名為第?7.5.3?節(jié) “基于JDK和CGLIB的代理”的章節(jié))。
interceptorNames
:Advisor
的字符串數(shù)組,可以包括攔截器或其它通知的名字。
順序是很重要的,排在前面的將被優(yōu)先服務。就是說列表里的第一個攔截器將能夠第一個攔截調(diào)用。
這里的名字是當前工廠中bean的名字,包括父工廠中bean的名字。這里你不能使用bean的引用因為這會導致ProxyFactoryBean
忽略通知的單例設(shè)置。
你可以把一個攔截器的名字加上一個星號作為后綴(*
)。這將導致這個應用程序里所有名字以星號之前部分開頭的通知器都被應用。
你可以在第?7.5.6?節(jié) “使用“全局”通知器” 發(fā)現(xiàn)一個使用這個特性的例子。
單例:工廠是否應該返回同一個對象,不論方法getObject()
被調(diào)用的多頻繁。
多個FactoryBean
實現(xiàn)都提供了這個方法。缺省值是true
。
如果你希望使用有狀態(tài)的通知--例如,有狀態(tài)的mixin--可以把單例屬性的值設(shè)置為false
來使用原型通知。
這個小節(jié)作為說明性文檔,解釋了對于一個目標對象(需要被代理的對象),ProxyFactryBean
是如何決定究竟創(chuàng)建一個基于JDK還是CGLIB的代理的。
ProxyFactoryBean
需要創(chuàng)建基于JDK還是CGLIB代理的具體行為在版本1.2.x和2.0中有所不同。
現(xiàn)在ProxyFactoryBean
在關(guān)于自動檢測接口方面使用了與TransactionProxyFactoryBean
相似的語義。
如果一個需要被代理的目標對象的類(后面將簡單地稱它為目標類)沒有實現(xiàn)任何接口,那么一個基于CGLIB的代理將被創(chuàng)建。
這是最簡單的場景,因為JDK代理是基于接口的,沒有接口意味著沒有使用JDK進行代理的可能。
在目標bean里將被插入探測代碼,通過interceptorNames
屬性給出了攔截器的列表。
注意一個基于CGLIB的代理將被創(chuàng)建即使ProxyFactoryBean
的proxyTargetClass
屬性被設(shè)置為false
。
(很明顯這種情況下對這個屬性進行設(shè)置是沒有意義的,最好把它從bean的定義中移除,因為雖然這只是個多余的屬性,但在許多情況下會引起混淆。)
如果目標類實現(xiàn)了一個(或者更多)接口,那么創(chuàng)建代理的類型將根據(jù)ProxyFactoryBean
的配置來決定。
如果ProxyFactoryBean
的proxyTargetClass
屬性被設(shè)為true
,那么一個基于CGLIB的代理將創(chuàng)建。
這樣的規(guī)定是有意義的,遵循了最小驚訝法則(保證了設(shè)定的一致性)。甚至當ProxyFactoryBean
的proxyInterfaces
屬性被設(shè)置為一個或者多個全限定接口名,
而proxyTargetClass
屬性被設(shè)置為true
仍然將實際使用基于CGLIB的代理。
如果ProxyFactoryBean
的proxyInterfaces
屬性被設(shè)置為一個或者多個全限定接口名,一個基于JDK的代理將被創(chuàng)建。
被創(chuàng)建的代理將實現(xiàn)所有在proxyInterfaces
屬性里被說明的接口;
如果目標類實現(xiàn)了全部在proxyInterfaces
屬性里說明的接口以及一些額外接口,返回的代理將只實現(xiàn)說明的接口而不會實現(xiàn)那些額外接口。
如果ProxyFactoryBean
的proxyInterfaces
屬性沒有被設(shè)置,
但是目標類實現(xiàn)了一個(或者更多)接口,那么ProxyFactoryBean
將自動檢測到這個目標類已經(jīng)實現(xiàn)了至少一個接口,
一個基于JDK的代理將被創(chuàng)建。被實際代理的接口將是目標類所實現(xiàn)的全部接口;
實際上,這和在proxyInterfaces
屬性中列出目標類實現(xiàn)的每個接口的情況是一樣的。
然而,這將顯著地減少工作量以及輸入錯誤的可能性。
讓我們看一個關(guān)于ProxyFactoryBean
的簡單例子。這個例子涉及:
一個將被代理的目標bean。在下面的例子里這個bean是“personTarget”。
被用來提供通知的一個通知器和一個攔截器。
一個AOP代理bean的定義,它說明了目標對象(personTarget bean)以及需要代理的接口,還包括需要被應用的通知。
<bean id="personTarget" class="com.mycompany.PersonImpl"> <property name="name"><value>Tony</value></property> <property name="age"><value>51</value></property> </bean> <bean id="myAdvisor" class="com.mycompany.MyAdvisor"> <property name="someProperty"><value>Custom string property value</value></property> </bean> <bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"> </bean> <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"><value>com.mycompany.Person</value></property> <property name="target"><ref local="personTarget"/></property> <property name="interceptorNames"> <list> <value>myAdvisor</value> <value>debugInterceptor</value> </list> </property> </bean>
注意interceptorNames
屬性接受一組字符串:當前工廠中攔截器或通知器bean的名字。
攔截器,通知器,前置, 后置和異常通知對象都可以在這里被使用。這里通知器的順序是很重要的。
你也許很奇怪為什么這個列表不保存bean的引用。理由是如果ProxyFactoryBean的singleton屬性被設(shè)置為false,它必須返回獨立的代理實例。 如果任何通知器本身是一個原型,則每次都返回一個獨立實例,因此它必須能夠從工廠里獲得原型的一個實例;保存一個引用是不夠的。
上面“person” bean的定義可以被用來取代一個Person接口的實現(xiàn),就像下面這樣:
Person person = (Person) factory.getBean("person");
在同一個IoC上下文中其它的bean可以對這個bean有基于類型的依賴,就像對一個普通的Java對象那樣:
<bean id="personUser" class="com.mycompany.PersonUser"> <property name="person"><ref local="person" /></property> </bean>
這個例子里的PersonUser
類將暴露一個類型為Person的屬性。就像我們關(guān)心的那樣,AOP代理可以透明地取代一個“真實”的person接口實現(xiàn)。
然而,它的類將是一個動態(tài)代理類。 它可以被轉(zhuǎn)型成Advised
接口(將在下面討論)。
就像下面這樣,你可以使用一個匿名內(nèi)部bean來隱藏目標和代理之間的區(qū)別。
僅僅ProxyFactoryBean
的定義有所不同;通知的定義只是由于完整性的原因而被包括進來:
<bean id="myAdvisor" class="com.mycompany.MyAdvisor"> <property name="someProperty"><value>Custom string property value</value></property> </bean> <bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/> <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"><value>com.mycompany.Person</value></property> <!-- Use inner bean, not local reference to target --> <property name="target"> <bean class="com.mycompany.PersonImpl"> <property name="name"><value>Tony</value></property> <property name="age"><value>51</value></property> </bean> </property> <property name="interceptorNames"> <list> <value>myAdvisor</value> <value>debugInterceptor</value> </list> </property> </bean>
對于只需要一個Person
類型對象的情況,這是有好處的:如果你希望阻止應用程序上下文的用戶獲取一個指向未通知對象的引用或者希望避免使用Spring IoC 自動織入 時的混淆。
按理說ProxyFactoryBean定義還有一個優(yōu)點是它是自包含的。然而,有時能夠從工廠里獲取未通知的目標也是一個優(yōu)點:例如,在某些測試場景里。
如果你需要代理一個類而不是代理一個或是更多接口,那么情況將是怎樣?
想象在我們上面的例子里,不存在Person
接口:我們需要通知一個叫做Person
的類,它沒有實現(xiàn)任何業(yè)務接口。
在這種情況下,你可以配置Spring使用CGLIB代理,而不是動態(tài)代理。這只需簡單地把上面ProxyFactoryBean的proxyTargetClass
屬性設(shè)為true。
雖然最佳方案是面向接口編程而不是類,但在與遺留代碼一起工作時,通知沒有實現(xiàn)接口的類的能力是非常有用的。
(通常情況下,Spring沒有任何規(guī)定。它只是讓你很容易根據(jù)實際情況選擇最好的解決方案,避免強迫使用特定方式)。
也許你希望你能夠在任何情況下都強制使用CGLIB,甚至在你使用接口的時候也這樣做。
CGLIB通過在運行時生成一個目標類的子類來進行代理工作。 Spring配置這個生成的子類對原始目標對象的方法調(diào)用進行托管:子類實現(xiàn)了裝飾器(Decorator)模式,把通知織入。
CGLIB的代理活動應當對用戶是透明的。然而,有一些問題需要被考慮:
Final
方法不可以被通知,因為它們不能被覆蓋。
你需要在你的類路徑里有CGLIB 2的庫;使用動態(tài)代理的話只需要JDK。
在CGLIB代理和動態(tài)代理之間的速度差別是很小的。在Spring 1.0中,動態(tài)代理會快一點點。但這點可能在將來被改變。 這種情況下,選擇使用何種代理時速度不應該成為決定性的理由。
通過在一個攔截器名后添加一個星號,所有bean名字與星號之前部分相匹配的通知都將被加入到通知器鏈中。這讓你很容易添加一組標準的“全局”通知器:
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="service"/> <property name="interceptorNames"> <list> <value>globa *</value> </list> </property> </bean> <bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/> <bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>