javascript
使用ProxyFactoryBean创建AOP代理 - Spring Framework reference 2.0.5 参考手册中文版
http://doc.javanb.com/spring-framework-reference-zh-2-0-5/ch07s05.html
7.5.?使用ProxyFactoryBean創建AOP代理
如果你正在使用Spring IoC容器(即ApplicationContext或BeanFactory)來管理你的業務對象--這正是你應該做的--你也許會想要使用Spring中關于AOP的FactoryBean。(記住使用工廠bean引入一個間接層之后,我們就可以創建不同類型的對象了)。
注意
Spring 2.0的AOP支持也在底層使用工廠bean。
在Spring里創建一個AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean。這個類對應用的切入點和通知提供了完整的控制能力(包括它們的應用順序)。然而如果你不需要這種控制,你會喜歡更簡單的方式。
7.5.1.?基礎
像其它的FactoryBean實現一樣,ProxyFactoryBean引入了一個間接層。如果你定義一個名為foo的ProxyFactoryBean, 引用foo的對象看到的將不是ProxyFactoryBean實例本身,而是一個ProxyFactoryBean實現里getObject() 方法所創建的對象。 這個方法將創建一個AOP代理,它包裝了一個目標對象。
使用ProxyFactoryBean或者其它IoC相關類帶來的最重要的好處之一就是創建AOP代理,這意味著通知和切入點也可以由IoC來管理。這是一個強大的功能并使得某些特定的解決方案成為可能, 而這些用其它AOP框架很難做到。例如,一個通知也許本身也要引用應用程序對象(不僅僅是其它AOP框架中也可以訪問的目標對象),這令你可以從依賴注射的可拔插特性中獲益。
7.5.2.?JavaBean屬性
通常情況下Spring提供了大多數的FactoryBean實現,ProxyFactoryBean類本身也是一個JavaBean。它的屬性被用來:
-
指定你希望代理的目標對象
-
指定是否使用CGLIB(查看下面叫做第?7.5.3?節 “基于JDK和CGLIB的代理”的小節)。
一些主要屬性從org.springframework.aop.framework.ProxyConfig里繼承下來(這個類是Spring里所有AOP代理工廠的父類)。這些主要屬性包括:
-
proxyTargetClass:這個屬性為true時,目標類本身被代理而不是目標類的接口。如果這個屬性值被設為true,CGLIB代理將被創建(可以參看下面名為第?7.5.3?節 “基于JDK和CGLIB的代理”的章節)。
-
optimize:用來控制通過CGLIB創建的代理是否使用激進的優化策略。除非完全了解AOP代理如何處理優化,否則不推薦用戶使用這個設置。目前這個屬性僅用于CGLIB代理;對于JDK動態代理(缺省代理)無效。
-
frozen:用來控制代理工廠被配置之后,是否還允許修改通知。缺省值為false(即在代理被配置之后,不允許修改代理的配置)。
-
exposeProxy:決定當前代理是否被保存在一個ThreadLocal中以便被目標對象訪問。(目標對象本身可以通過MethodInvocation來訪問,因此不需要ThreadLocal。) 如果個目標對象需要獲取代理而且exposeProxy屬性被設為true,目標對象可以使用AopContext.currentProxy()方法。
-
aopProxyFactory:使用AopProxyFactory的實現。這提供了一種方法來自定義是否使用動態代理,CGLIB或其它代理策略。 缺省實現將根據情況選擇動態代理或者CGLIB。一般情況下應該沒有使用這個屬性的需要;它是被設計來在Spring 1.1中添加新的代理類型的。
ProxyFactoryBean中需要說明的其它屬性包括:
-
proxyInterfaces:需要代理的接口名的字符串數組。如果沒有提供,將為目標類使用一個CGLIB代理(也可以查看下面名為第?7.5.3?節 “基于JDK和CGLIB的代理”的章節)。
-
interceptorNames:Advisor的字符串數組,可以包括攔截器或其它通知的名字。順序是很重要的,排在前面的將被優先服務。就是說列表里的第一個攔截器將能夠第一個攔截調用。
這里的名字是當前工廠中bean的名字,包括父工廠中bean的名字。這里你不能使用bean的引用因為這會導致ProxyFactoryBean忽略通知的單例設置。
你可以把一個攔截器的名字加上一個星號作為后綴(*)。這將導致這個應用程序里所有名字以星號之前部分開頭的advisor都被應用。你可以在第?7.5.6?節 “使用“全局”advisor” 發現一個使用這個特性的例子。
-
單例:工廠是否應該返回同一個對象,不論方法getObject()被調用的多頻繁。多個FactoryBean實現都提供了這個方法。缺省值是true。如果你希望使用有狀態的通知--例如,有狀態的mixin--可以把單例屬性的值設置為false來使用原型通知。
7.5.3.?基于JDK和CGLIB的代理
這個小節作為說明性文檔,解釋了對于一個目標對象(需要被代理的對象),ProxyFactryBean是如何決定究竟創建一個基于JDK還是CGLIB的代理的。
注意
ProxyFactoryBean需要創建基于JDK還是CGLIB代理的具體行為在版本1.2.x和2.0中有所不同。現在ProxyFactoryBean在關于自動檢測接口方面使用了與TransactionProxyFactoryBean相似的語義。
如果一個需要被代理的目標對象的類(后面將簡單地稱它為目標類)沒有實現任何接口,那么一個基于CGLIB的代理將被創建。這是最簡單的場景,因為JDK代理是基于接口的,沒有接口意味著沒有使用JDK進行代理的可能。 在目標bean里將被插入探測代碼,通過interceptorNames屬性給出了攔截器的列表。注意一個基于CGLIB的代理將被創建即使ProxyFactoryBean的proxyTargetClass屬性被設置為false。 (很明顯這種情況下對這個屬性進行設置是沒有意義的,最好把它從bean的定義中移除,因為雖然這只是個多余的屬性,但在許多情況下會引起混淆。)
如果目標類實現了一個(或者更多)接口,那么創建代理的類型將根據ProxyFactoryBean的配置來決定。
如果ProxyFactoryBean的proxyTargetClass屬性被設為true,那么一個基于CGLIB的代理將創建。這樣的規定是有意義的,遵循了最小驚訝法則(保證了設定的一致性)。 甚至當ProxyFactoryBean的proxyInterfaces屬性被設置為一個或者多個全限定接口名,而proxyTargetClass屬性被設置為true仍然將實際使用基于CGLIB的代理。
如果ProxyFactoryBean的proxyInterfaces屬性被設置為一個或者多個全限定接口名,一個基于JDK的代理將被創建。被創建的代理將實現所有在proxyInterfaces屬性里被說明的接口;如果目標類實現了全部在proxyInterfaces屬性里說明的接口以及一些額外接口,返回的代理將只實現說明的接口而不會實現那些額外接口。
如果ProxyFactoryBean的proxyInterfaces屬性沒有被設置,但是目標類實現了一個(或者更多)接口,那么ProxyFactoryBean將自動檢測到這個目標類已經實現了至少一個接口, 一個基于JDK的代理將被創建。被實際代理的接口將是目標類所實現的全部接口;實際上,這和在proxyInterfaces屬性中列出目標類實現的每個接口的情況是一樣的。然而,這將顯著地減少工作量以及輸入錯誤的可能性。
7.5.4.?對接口進行代理
讓我們看一個關于ProxyFactoryBean的簡單例子。這個例子涉及:
-
一個將被代理的目標bean。在下面的例子里這個bean是“personTarget”。
-
被用來提供通知的一個advisor和一個攔截器。
-
一個AOP代理bean的定義,它說明了目標對象(personTarget bean)以及需要代理的接口,還包括需要被應用的通知。
注意interceptorNames屬性接受一組字符串:當前工廠中攔截器或advisorbean的名字。攔截器,advisor,前置, 后置和異常通知對象都可以在這里被使用。這里advisor的順序是很重要的。
注意
你也許很奇怪為什么這個列表不保存bean的引用。理由是如果ProxyFactoryBean的singleton屬性被設置為false,它必須返回獨立的代理實例。如果任何advisor本身是一個原型,則每次都返回一個獨立實例,因此它必須能夠從工廠里獲得原型的一個實例;保存一個引用是不夠的。
上面“person” bean的定義可以被用來取代一個Person接口的實現,就像下面這樣:
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的屬性。就像我們關心的那樣,AOP代理可以透明地取代一個“真實”的person接口實現。然而,它的類將是一個動態代理類。 它可以被轉型成Advised接口(將在下面討論)。
就像下面這樣,你可以使用一個匿名內部bean來隱藏目標和代理之間的區別。僅僅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.ProxyFactor Bean"><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定義還有一個優點是它是自包含的。然而,有時能夠從工廠里獲取未通知的目標也是一個優點:例如,在某些測試場景里。
7.5.5.?對類進行代理
如果你需要代理一個類而不是代理一個或是更多接口,那么情況將是怎樣?
想象在我們上面的例子里,不存在Person接口:我們需要通知一個叫做Person的類,它沒有實現任何業務接口。在這種情況下,你可以配置Spring使用CGLIB代理,而不是動態代理。 這只需簡單地把上面ProxyFactoryBean的proxyTargetClass屬性設為true。雖然最佳方案是面向接口編程而不是類,但在與遺留代碼一起工作時,通知沒有實現接口的類的能力是非常有用的。(通常情況下,Spring沒有任何規定。它只是讓你很容易根據實際情況選擇最好的解決方案,避免強迫使用特定方式)。
也許你希望你能夠在任何情況下都強制使用CGLIB,甚至在你使用接口的時候也這樣做。
CGLIB通過在運行時生成一個目標類的子類來進行代理工作。Spring配置這個生成的子類對原始目標對象的方法調用進行托管:子類實現了裝飾器(Decorator)模式,把通知織入。
CGLIB的代理活動應當對用戶是透明的。然而,有一些問題需要被考慮:
-
Final方法不可以被通知,因為它們不能被覆蓋。
-
你需要在你的類路徑里有CGLIB 2的庫;使用動態代理的話只需要JDK。
在CGLIB代理和動態代理之間的速度差別是很小的。在Spring 1.0中,動態代理會快一點點。但這點可能在將來被改變。這種情況下,選擇使用何種代理時速度不應該成為決定性的理由。
7.5.6.?使用“全局”advisor
通過在一個攔截器名后添加一個星號,所有bean名字與星號之前部分相匹配的通知都將被加入到advisor鏈中。這讓你很容易添加一組標準的“全局”advisor:
<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"/>?
總結
以上是生活随笔為你收集整理的使用ProxyFactoryBean创建AOP代理 - Spring Framework reference 2.0.5 参考手册中文版的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Clojure 学习
- 下一篇: 第 5 章 Spring AOP: Sp