[轉]使用ProxyFactoryBean建立AOP代理

http://doc.javanb.com/spring-framework-reference-zh-2-0-5/html

 

7.5. 使用ProxyFactoryBean建立AOP代理 - Spring Framework reference 2.0.5 參考手冊中文版

7.5. 使用ProxyFactoryBean建立AOP代理

若是你正在使用Spring IoC容器(即ApplicationContext或BeanFactory)來管理你的業務對象--這正是你應該作的--你也許會想要使用Spring中關於AOP的FactoryBean。(記住使用工廠bean引入一個間接層以後,咱們就能夠建立不一樣類型的對象了)。java

注意

Spring 2.0的AOP支持也在底層使用工廠bean。spring

在Spring裏建立一個AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean。這個類對應用的切入點和通知提供了完整的控制能力(包括它們的應用順序)。然而若是你不須要這種控制,你會喜歡更簡單的方式。編程

7.5.1. 基礎

像其它的FactoryBean實現同樣,ProxyFactoryBean引入了一個間接層。若是你定義一個名爲fooProxyFactoryBean, 引用foo的對象看到的將不是ProxyFactoryBean實例自己,而是一個ProxyFactoryBean實現裏getObject() 方法所建立的對象。 這個方法將建立一個AOP代理,它包裝了一個目標對象。數組

使用ProxyFactoryBean或者其它IoC相關類帶來的最重要的好處之一就是建立AOP代理,這意味着通知和切入點也能夠由IoC來管理。這是一個強大的功能並使得某些特定的解決方案成爲可能, 而這些用其它AOP框架很難作到。例如,一個通知也許自己也要引用應用程序對象(不只僅是其它AOP框架中也能夠訪問的目標對象),這令你能夠從依賴注射的可拔插特性中獲益。框架

7.5.2. JavaBean屬性

一般狀況下Spring提供了大多數的FactoryBean實現,ProxyFactoryBean類自己也是一個JavaBean。它的屬性被用來:測試

一些主要屬性從org.springframework.aop.framework.ProxyConfig裏繼承下來(這個類是Spring裏全部AOP代理工廠的父類)。這些主要屬性包括:debug

  • 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的代理」的章節)。

  • interceptorNamesAdvisor的字符串數組,能夠包括攔截器或其它通知的名字。順序是很重要的,排在前面的將被優先服務。就是說列表裏的第一個攔截器將可以第一個攔截調用。

    這裏的名字是當前工廠中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的代理將被建立即便ProxyFactoryBeanproxyTargetClass屬性被設置爲false。 (很明顯這種狀況下對這個屬性進行設置是沒有意義的,最好把它從bean的定義中移除,由於雖然這只是個多餘的屬性,但在許多狀況下會引發混淆。)

若是目標類實現了一個(或者更多)接口,那麼建立代理的類型將根據ProxyFactoryBean的配置來決定。

若是ProxyFactoryBeanproxyTargetClass屬性被設爲true,那麼一個基於CGLIB的代理將建立。這樣的規定是有意義的,遵循了最小驚訝法則(保證了設定的一致性)。 甚至當ProxyFactoryBeanproxyInterfaces屬性被設置爲一個或者多個全限定接口名,而proxyTargetClass屬性被設置爲true仍然實際使用基於CGLIB的代理。

若是ProxyFactoryBeanproxyInterfaces屬性被設置爲一個或者多個全限定接口名,一個基於JDK的代理將被建立。被建立的代理將實現全部在proxyInterfaces屬性裏被說明的接口;若是目標類實現了所有在proxyInterfaces屬性裏說明的接口以及一些額外接口,返回的代理將只實現說明的接口而不會實現那些額外接口。

若是ProxyFactoryBeanproxyInterfaces屬性沒有被設置,可是目標類實現了一個(或者更多)接口,那麼ProxyFactoryBean將自動檢測到這個目標類已經實現了至少一個接口, 一個基於JDK的代理將被建立。被實際代理的接口將是目標類所實現的所有接口;實際上,這和在proxyInterfaces屬性中列出目標類實現的每一個接口的狀況是同樣的。然而,這將顯著地減小工做量以及輸入錯誤的可能性。

7.5.4. 對接口進行代理

讓咱們看一個關於ProxyFactoryBean的簡單例子。這個例子涉及:

  • 一個將被代理的目標bean。在下面的例子裏這個bean是「personTarget」。

  • 被用來提供通知的一個advisor和一個攔截器。

  • 一個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屬性接受一組字符串:當前工廠中攔截器或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"/>
相關文章
相關標籤/搜索