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