OMG,到底在尋找什么..................
          (構造一個完美的J2EE系統所需要的完整知識體系)
          posts - 198,  comments - 37,  trackbacks - 0

          原貼地址:http://book.csdn.net/bookfiles/111/1001113463.shtml

          一.AOP概念

          介紹完
          IoC 之后,我們來介紹另外一個重要的概念: AOP(Aspect Oriented Programming) ,也就是面向方面編程的技術。 AOP 基于 IoC 基礎,是對 OOP 的有益補充。

          AOP 將應用系統分為兩部分,核心業務邏輯 Core business concerns )及橫向的通用邏輯, 也就是所謂的方面 Crosscutting enterprise concerns ,例如,所有大中型應用都要涉及到的持久化管理( Persistent )、事務管理( Transaction Management )、安全管理( Security )、 日志管理( Logging )和調試管理( Debugging )等。

          AOP 正在成為軟件開發的下一個光環。使用 AOP ,你可以將處理 aspect 的代碼注入主程序,通常主程序的主要目的并不在于處理這些 aspect 。 AOP 可以防止代碼混亂。

          Spring framework 是很有前途的 AOP 技術。作為一種非侵略性的、輕型的 AOP framework ,你無需使用預編譯器或其他的元標簽,便可以在 Java 程序中使用它。這意味著開發團隊里只需一人要對付 AOP framework ,其他人還是像往常一樣編程。

          6.3.1? A OP 概念

          讓我們從定義一些重要的 AOP 概念開始。

          方面( Aspect ):一個關注點的模塊化,這個關注點實現可能另外橫切多個對象。事務管理是 J2EE 應用中一個很好的橫切關注點例子。方面用 Spring Advisor 或攔截器實現。

          連接點( Joinpoint ):程序執行過程中明確的點,如方法的調用或特定的異常被拋出。

          通知( Advice ):在特定的連接點, AOP 框架執行的動作。各種類型的通知包括“ around ”、“ before ”和“ throws ”通知。通知類型將在下面討論。許多 AOP 框架包括 Spring 都是以攔截器做通知模型,維護一個“圍繞”連接點的攔截器鏈。

          切入點( Pointcut ):指定一個通知將被引發的一系列連接點的集合。 AOP 框架必須允許開發者指定切入點,例如,使用正則表達式。

          引入( Introduction ):添加方法或字段到被通知的類。 Spring 允許引入新的接口到任何被通知的對象。例如,你可以使用一個引入使任何對象實現 IsModified 接口,來簡化緩存。

          目標對象( Target Object ):包含連接點的對象,也被稱作被通知或被代理對象。

          AOP 代理( AOP Proxy ): AOP 框架創建的對象,包含通知。在 Spring 中, AOP 代理可以是 JDK 動態代理或 CGLIB 代理。

          編織( Weaving ):組裝方面來創建一個被通知對象。這可以在編譯時完成(例如使用 AspectJ 編譯器),也可以在運行時完成。 Spring 和其他純 Java AOP 框架一樣,在運行時完成織入。

          各種通知類型包括:

          ? Around 通知:包圍一個連接點的通知,如方法調用。這是最強大的通知。 Aroud 通知在方法調用前后完成自定義的行為,它們負責選擇繼續執行連接點或通過返回它們自己的返回值或拋出異常來短路執行。

          ? Before 通知:在一個連接點之前執行的通知,但這個通知不能阻止連接點前的執行(除非它拋出一個異常)。

          ? Throws 通知:在方法拋出異常時執行的通知。 Spring 提供強制類型的 Throws 通知,因此你可以書寫代碼捕獲感興趣的異常(和它的子類),不需要從 Throwable Exception 強制類型轉換。

          ? After returning 通知:在連接點正常完成后執行的通知,例如,一個方法正常返回,沒有拋出異常。

          Around 通知是最通用的通知類型。大部分基于攔截的 AOP 框架(如 Nanning Jboss 4 只提供 Around 通知。

          如同 AspectJ , Spring 提供所有類型的通知,我們推薦你使用最為合適的通知類型來實現需要的行為。例如,如果只是需要用一個方法的返回值來更新緩存,你最好實現一個 after returning 通知,而不是 around 通知,雖然 around 通知也能完成同樣的事情。使用最合適的通知類型使編程模型變得簡單,并能減少潛在錯誤。例如,你不需要調用在 around 通知中所需使用的 MethodInvocation proceed() 方法,因此就調用失敗。

          切入點的概念是 AOP 的關鍵,它使 AOP 區別于其他使用攔截的技術。切入點使通知獨立于 OO 的層次選定目標。例如,提供聲明式事務管理的 around 通知可以被應用到跨越多個對象的一組方法上。 因此切入點構成了 AOP 的結構要素。

          下面讓我們實現一個 Spring AOP 的例子。在這個例子中,我們將實現一個 before advice ,這意味著 advice 的代碼在被調用的 public 方法開始前被執行。以下是這個 before advice 的實現代碼。

          package com.ascenttech.springaop.test;
          import java.lang.reflect.Method;
          import org.springframework.aop.MethodBeforeAdvice;

          public class TestBeforeAdvice implements MethodBeforeAdvice {
          ???public void before(Method m, Object[] args, Object target) ?throws Throwable {
          ?????System.out.println("Hello world! (by " + this.getClass().getName() ?+ ")");
          ????}
          }

          接口 MethodBeforeAdvice 只有一個方法 before 需要實現,它定義了 advice 的實現。 before 方法共用 3 個參數,它們提供了相當豐富的信息。參數 Method m advice 開始后執行的方法,方法名稱可以用作判斷是否執行代碼的條件。 Object[] args 是傳給被調用的 public 方法的參數數組。當需要記日志時,參數 args 和被執行方法的名稱都是非常有用的信息。你也可以改變傳給 m 的參數,但要小心使用這個功能;編寫最初主程序的程序員并不知道主程序可能會和傳入參數的發生沖突。 Object target 是執行方法 m 對象的引用。

          在下面的 BeanImpl 類中,每個 public 方法調用前,都會執行 advice ,代碼如下。

          package com.ascenttech.springaop.test;
          public class BeanImpl implements Bean {

          ?public void theMethod() {
          ???
          ?System.out.println(this.getClass().getName() ? + "." + new Exception().getStackTrace()? [0].getMethodName???() ?+??"()"??+??"says HELLO!");????
          ?}
          }

          BeanImpl 實現了下面的接口 Bean ,代碼如下。

          package com.ascenttech.springaop.test;
          public interface Bean {
          ?public void theMethod();
          }

          雖然不是必須使用接口,但面向接口而不是面向實現編程是良好的編程實踐, Spring 也鼓勵這樣做。

          pointcut advice 通過配置文件來實現,因此,接下來你只需編寫主方法的 Java 代碼,代碼如下。

          package com.ascenttech.springaop.test;
          import org.springframework.context.ApplicationContext;
          import org.springframework.context.support.FileSystemXmlApplicationContext;

          public class Main {
          ?public static void main(String[] args) {

          ? //Read the configuration file
          ? ApplicationContext ctx = new FileSystemXmlApplicationContext("springconfig.xml");

          ? //Instantiate an object
          ? Bean x = (Bean) ctx.getBean("bean");

          ? //Execute the public method of the bean (the test)
          ? x.theMethod();
          ?}
          }

          我們從讀入和處理配置文件開始,接下來馬上要創建它。這個配置文件將作為粘合程序不同部分的“膠水”。讀入和處理配置文件后,我們會得到一個創建工廠 ctx ,任何一個 Spring 管理的對象都必須通過這個工廠來創建。對象通過工廠創建后便可正常使用。

          僅僅用配置文件便可把程序的每一部分組裝起來,代碼如下。

          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework. org/dtd/spring-beans.dtd">
          <beans>
          ?<!--CONFIG-->
          ?<bean id="bean" class="org.springframework.aop.framework.ProxyFactoryBean">
          ????<property name="proxyInterfaces">
          ?????????<value>com.ascenttech.springaop.test.Bean</value>
          ?????</property>

          ? <property name="target">
          ???
          ? <ref local="beanTarget"/>
          ? </property>

          ? <property name="interceptorNames">
          ?? <list>
          ??? <value>theAdvisor</value>
          ?? </list>
          ? </property>

          ?</bean>?

          ?<!--CLASS-->?
          ?<bean id="beanTarget" class="com.ascenttech.springaop.test.BeanImpl"/>
          ?<!--ADVISOR-->
          ?<!--Note: An advisor assembles pointcut and advice-->

          ?<bean id="theAdvisor" class="org.springframework.aop.support.RegexpMethod PointcutAdvisor">
          ? <property name="advice">
          ????? <ref local="theBeforeAdvice"/>
          ? </property>

          ? <property name="pattern">
          ???
          ?? <value>com\.ascenttech\.springaop\.test\.Bean\.theMethod</value>
          ? </property>

          ?</bean>
          ?<!--ADVICE-->

          ?<bean id="theBeforeAdvice" class="com.ascenttech.springaop.test.TestBefore Advice"/>
          </beans>

          4 bean 定義的次序并不重要。我們現在有了一個 advice 、一個包含了正則表達式 pointcut advisor 、一個主程序類和一個配置好的接口,通過工廠 ctx ,這個接口返回自己本身實現的一個引用。

          BeanImpl TestBeforeAdvice 都是直接配置。我們用一個惟一的 ID 創建一個 bean 元素,并指定了一個實現類,這就是全部的工作。

          advisor 通過 Spring framework 提供的一個 RegexMethodPointcutAdvisor 類來實現。我們用 advisor 的第一個屬性來指定它所需的 advice-bean ,第二個屬性則用正則表達式定義了 pointcut ,確保良好的性能和易讀性。

          最后配置的是 bean ,它可以通過一個工廠來創建。 bean 的定義看起來比實際上要復雜。 bean ProxyFactoryBean 的一個實現,它是 Spring framework 的一部分。這個 bean 的行為通過以下的 3 個屬性來定義。

          屬性 proxyInterface 定義了接口類。

          屬性 target 指向本地配置的一個 bean ,這個 bean 返回一個接口的實現。

          屬性 interceptorNames 是惟一允許定義一個值列表的屬性,這個列表包含所有需要在 beanTarget 上執行的 advisor 。注意, advisor 列表的次序是非常重要的。

          二.Spring的切入點,通知,advisor

          讓我們看看 Spring 如何處理切入點這個重要的概念。

          1 .概念

          Spring 的切入點模型能夠使切入點獨立于通知類型被重用。 同樣的切入點有可能接受不同的通知。

          org.springframework.aop.Pointcut 接口是重要的接口,用來指定通知到特定的類和方法目標,完整的接口定義如下。

          public interface Pointcut {
          ??? ClassFilter getClassFilter();
          ??? MethodMatcher getMethodMatcher();
          }

          Pointcut 接口分成兩個部分有利于重用類和方法的匹配部分,并且組合細粒度的操作(如和另一個方法匹配器執行一個“并”的操作)。

          ClassFilter 接口被用來將切入點限制到一個給定的目標類的集合。如果 matches() 永遠返回 true ,所有的目標類都將被匹配。

          public interface ClassFilter {
          ?? boolean matches(Class clazz);
          }

          MethodMatcher 接口通常更加重要,完整的接口如下。

          public interface MethodMatcher {
          ??? boolean matches(Method m, Class targetClass);
          ??? boolean isRuntime();
          ??? boolean matches(Method m, Class targetClass, Object[] args);
          }

          matches(Method, Class) 方法被用來測試這個切入點是否匹配目標類的給定方法。這個測試可以在 AOP 代理創建的時候執行,避免在所有方法調用時都需要進行測試。如果 2 個參數的匹配方法對某個方法返回 true ,并且 MethodMatcher isRuntime() 也返回 true ,那么 3 個參數的匹配方法將在每次方法調用的時候被調用。這使切入點能夠在目標通知被執行之前立即查看傳遞給方法調用的參數。

          大部分 MethodMatcher 都是靜態的,意味著 isRuntime() 方法返回 false 。這種情況下, 3 個參數的匹配方法永遠不會被調用。

          如果可能,盡量使切入點是靜態的,使當 AOP 代理被創建時, AOP 框架能夠緩存切入點的測試結果。

          2 .切入點的運算

          Spring 支持的切入點的運算有。

          并表示只要任何一個切入點匹配的方法。交表示兩個切入點都要匹配的方法。并通常比較有用。

          切入點可以用 org.springframework.aop.support.Pointcuts 類的靜態方法來組合,或者使用同一個包中的 ComposablePointcut 類。

          3 .實用切入點實現

          Spring 提供幾個實用的切入點實現,一些可以直接使用,另一些需要子類化來實現應用相關的切入點。

          1 )靜態切入點

          靜態切入點只基于方法和目標類,而不考慮方法的參數。靜態切入點足夠滿足大多數情況的使用。 Spring 可以只在方法第一次被調用的時候計算靜態切入點,不需要在每次方法調用的時候計算。

          讓我們看一下 Spring 提供的一些靜態切入點的實現。

          正則表達式切入點

          一個很顯然的指定靜態切入點的方法是正則表達式。除了 Spring 以外,其他的 AOP 框架也實現了這一點。 org.springframework.aop.support.RegexpMethodPointcut 是一個通用的正則表達式切入點,它使用 Perl 5 的正則表達式的語法。

          使用這個類你可以定義一個模式的列表。如果任何一個匹配,那個切入點將被計算成 true (所以,結果相當于是這些切入點的并集)。

          用法如下。

          <bean id="settersAndAbsquatulatePointcut"?
          ??? class="org.springframework.aop.support.RegexpMethodPointcut">

          ??? <property name="patterns">
          ??????? <list>
          ????????????<value>.*get.*</value>
          ??????????? <value>.*absquatulate</value>
          ??????? </list>
          ??? </property>
          </bean>

          RegexpMethodPointcut 一個實用子類, RegexpMethodPointcutAdvisor 允許我們同時引用一個通知(通知可以是攔截器、 before 通知、 throws 通知等)。這簡化了 bean 的裝配,因為一個 bean 可以同時當作切入點和通知,如下所示。

          <bean id="settersAndAbsquatulateAdvisor"?
          ??? class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">

          ??? <property name="interceptor">
          ??????? <ref local="beanNameOfAopAllianceInterceptor"/>
          ??? </property>

          ??? <property name="patterns">
          ??????? <list>
          ??????????? <value>.*get.*</value>
          ??????????? <value>.*absquatulate</value>
          ??????? </list>
          ??? </property>

          </bean>

          RegexpMethodPointcutAdvisor 可以用于任何通知類型。 RegexpMethodPointcut 類需要 Jakarta ORO 正則表達式包。

          屬性驅動的切入點

          一類重要的靜態切入點是元數據驅動的切入點。它使用元數據屬性的值,典型地,使用源代碼級元數據。

          2 )動態切入點

          動態切入點的演算代價比靜態切入點高得多。它們不僅考慮靜態信息,還要考慮方法的參數。這意味著它們必須在每次方法調用的時候都被計算,并且不能緩存結果,因為參數是變化的。

          控制流切入點

          Spring 的控制流切入點概念上和 AspectJ cflow 切入點一致,雖然沒有其那么強大(當前沒有辦法指定一個切入點在另一個切入點后執行)。一個控制流切入點匹配當前的調用棧。例如,連接點被 com.mycompany.web 包或者 SomeCaller 類中一個方法調用的時候,觸發該切入點??刂屏髑腥朦c的實現類是 org.springframework.aop.support.ControlFlowPointcut 。

          4 .切入點超類

          Spring 提供非常實用的切入點的超類幫助你實現你自己的切入點。

          因為靜態切入點非常實用,你很可能子類化 StaticMethodMatcherPointcut ,如下所示。 這只需要實現一個抽象方法(雖然可以改寫其他的方法來自定義行為)。

          class TestStaticPointcut extends StaticMethodMatcherPointcut {

          ??? public boolean matches(Method m, Class targetClass) {
          ??????? // return true if custom criteria match
          ??? }

          }

          當然也有動態切入點的超類。

          Spring 1.0 RC2 或以上版本,自定義切入點可以用于任何類型的通知。

          5 .自定義切入點

          因為 Spring 中的切入點是 Java 類,而不是語言特性(如 AspectJ ),因此可以定義自定義切入點,無論靜態還是動態。但是,沒有直接支持用 AspectJ 語法書寫的復雜的切入點表達式。不過, Spring 的自定義切入點也可以任意的復雜。

          現在讓我們看看 Spring AOP 是如何處理通知的。

          1 .通知的生命周期

          Spring 的通知可以跨越多個被通知對象共享,或者每個被通知對象有自己的通知。這分別對應 per-class per-instance 通知。

          Per-class 通知使用最為廣泛。它適合于通用的通知,如事務 adisor 。它們不依賴被代理的對象的狀態,也不添加新的狀態。它們僅僅作用于方法和方法的參數。

          Per-instance 通知適合于導入,來支持混入( mixin )。在這種情況下,通知添加狀態到被代理的對象。

          可以在同一個 AOP 代理中混合使用共享和 per-instance 通知。

          2 Spring 中通知類型

          Spring 提供幾種現成的通知類型并可擴展提供任意的通知類型。讓我們看看基本概念和標準的通知類型。

          1 Interception around advice

          Spring 中最基本的通知類型是 interception around advice 。

          Spring 使用方法攔截器的 around 通知是和 AOP 聯盟接口兼容的。實現 around 通知的類需要實現接口 MethodInterceptor 。

          public interface MethodInterceptor extends Interceptor { ?
          ?? Object invoke(MethodInvocation invocation) throws Throwable;
          }

          invoke() 方法的 MethodInvocation 參數暴露將被調用的方法、目標連接點、 AOP 代理和傳遞給被調用方法的參數。 invoke() 方法應該返回調用的結果:連接點的返回值。

          一個簡單的 MethodInterceptor 實現看起來如下。

          public class DebugInterceptor implements MethodInterceptor {

          ??? public Object invoke(MethodInvocation invocation) throws Throwable {
          ??????? System.out.println("Before: invocation=[" + invocation + "]");
          ??????? Object rval = invocation.proceed();
          ??????? System.out.println("Invocation returned");
          ??????? return rval;
          ??? }
          }

          注意 MethodInvocation proceed() 方法的調用。這個調用會應用到目標連接點的攔截器鏈中的每一個攔截器。大部分攔截器會調用這個方法,并返回它的返回值。但是,一個 MethodInterceptor ,和任何 around 通知一樣,可以返回不同的值或者拋出一個異常,而不調用 proceed 方法。但是,沒有好的原因你要這么做。

          MethodInterceptor 提供了和其他 AOP 聯盟的兼容實現的交互能力。這一節下面要討論的其他的通知類型實現了 AOP 公共的概念,但是以 Spring 特定的方式。雖然使用特定通知類型有很多優點,但如果你需要在其他的 AOP 框架中使用,請堅持使用 MethodInterceptor around 通知類型。注意,目前切入點不能和其他框架交互操作,并且 AOP 聯盟目前也沒有定義切入點接口。

          2 Before 通知

          Before 通知 是一種簡單的通知類型。 這個通知不需要一個 MethodInvocation 對象,因為它只在進入一個方法前被調用。

          Before 通知的主要優點是它不需要調用 proceed() 方法,因此沒有無意中忘掉繼續執行攔截器鏈的可能性。

          MethodBeforeAdvice 接口如下所示( Spring API 設計允許成員變量的 Before 通知,雖然一般的對象都可以應用成員變量攔截,但 Spring 有可能永遠不會實現它)。

          public interface MethodBeforeAdvice extends BeforeAdvice {
          ??? void before(Method m, Object[] args, Object target) throws Throwable;
          }

          注意,返回類型是 void Before 通知可以在連接點執行之前插入自定義的行為,但是不能改變返回值。如果一個 Before 通知拋出一個異常,這將中斷攔截器鏈的進一步執行。這個異常將沿著攔截器鏈后退著向上傳播。如果這個異常是 unchecked 的,或者出現在被調用的方法的簽名中,它將會被直接傳遞給客戶代碼;否則,它將被 AOP 代理包裝到一個 unchecked 的異常里。

          下面是 Spring 中一個 Before 通知的例子,這個例子計數所有正常返回的方法。

          public class CountingBeforeAdvice implements MethodBeforeAdvice {

          ??? private int count;
          ??? public void before(Method m, Object[] args, Object target) throws Throwable {
          ??????? ++count;
          ??? }

          ??? public int getCount() {?
          ??????? return count;?
          ??? }

          }

          Before 通知可以被用于任何類型的切入點。

          3 Throws 通知

          如果連接點拋出異常, Throws 通知 在連接點返回后被調用。 Spring 提供強類型的 Throws 通知。注意,這意味著 org.springframework.aop.ThrowsAdvice 接口不包含任何方法,它是一個標記接口,標識給定的對象實現了一個或多個強類型的 Throws 通知方法。這些方法形式如下。

          afterThrowing([Method], [args], [target], subclassOfThrowable)

          只有最后一個參數是必需的。這樣從 1 個參數到 4 個參數,依賴于通知是否對方法和方法的參數感興趣。下面是 Throws 通知的例子。

          如果拋出 RemoteException 異常(包括子類),這個通知會被調用。

          public? class RemoteThrowsAdvice implements ThrowsAdvice {

          ??? public void afterThrowing(RemoteException ex) throws Throwable {
          ??????? // Do something with remote exception
          ??? }
          }

          如果拋出 ServletException 異常,下面的通知會被調用。和上面的通知不一樣,它聲明了 4 個參數,所以它可以訪問被調用的方法、方法的參數和目標對象。

          public static class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

          ??? public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
          ??????? // Do something will all arguments
          ?? }
          }

          最后一個例子演示了如何在一個類中使用兩個方法來同時處理 RemoteException ServletException 異常。任意個數的 throws 方法可以被組合在一個類中。

          public static class CombinedThrowsAdvice implements ThrowsAdvice {

          ??? public void afterThrowing(RemoteException ex) throws Throwable {
          ??????? // Do something with remote exception
          ??? }

          ??? public void afterThrowing(Method m, Object[] args, Object target, ServletException ex){ ?
          ?????? // Do something will all arguments
          ??? }

          }

          Throws 通知可被用于任何類型的切入點。

          4 After Returning 通知

          Spring 中的 After Returning 通知必須實現 org.springframework.aop.AfterReturningAdvice 接口,如下所示。

          public interface AfterReturningAdvice extends Advice {
          ??? void afterReturning(Object returnValue, Method m, Object[] args, Object target)? throws Throwable;

          }

          After Returning 通知可以訪問返回值(不能改變)、被調用的方法、方法的參數和目標對象。

          下面的 After Returning 通知統計所有成功的沒有拋出異常的方法調用。

          public class CountingAfterReturningAdvice implements AfterReturningAdvice {

          ??? private int count;
          ??? public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable {
          ??????? ++count;
          ??? }

          ??? public int getCount() {
          ??????? return count;
          ????}
          }

          這方法不改變執行路徑。如果它拋出一個異常,這個異常而不是返回值將被沿著攔截器鏈向上拋出。

          After returning 通知可被用于任何類型的切入點。

          5 Introduction 通知

          Spring Introduction 通知看作一種特殊類型的攔截通知。

          Introduction 需要實現 IntroductionAdvisor IntroductionInterceptor 接口。

          public interface IntroductionInterceptor extends MethodInterceptor {
          ??? boolean implementsInterface(Class intf);
          }

          繼承自 AOP 聯盟 MethodInterceptor 接口的 invoke() 方法必須實現導入,也就是說,如果被調用的方法是在導入的接口中,導入攔截器負責處理這個方法調用,它不能調用 proceed() 方法。

          Introduction 通知不能被用于任何切入點,因為它只能作用于類層次上,而不是方法。你可以只用 InterceptionIntroductionAdvisor 來實現導入通知,它有下面的方法。

          public interface InterceptionIntroductionAdvisor extends InterceptionAdvisor {

          ??? ClassFilter getClassFilter();
          ??? IntroductionInterceptor getIntroductionInterceptor();
          ??? Class[] getInterfaces();
          }

          這里沒有 MethodMatcher ,因此也沒有和導入通知關聯的切入點,只有類過濾是合乎邏輯的。

          getInterfaces() 方法返回 advisor 導入的接口。

          讓我們看看一個來自 Spring 測試套件中的簡單例子。我們假設想要導入下面的接口到一個或者多個對象中。

          public interface Lockable {
          ??? void lock();
          ??? void unlock();
          ??? boolean locked();
          }

          在這個例子中,我們想要能夠將被通知對象類型轉換為 Lockable ,不管它們的類型,并且調用 lock unlock 方法。如果我們調用 lock() 方法,我們希望所有 setter 方法拋出 LockedException 異常。這樣我們能添加一個方面使對象 不可變,而它們不需要知道這一點,這是一個很好的 AOP 例子。

          首先,我們需要一個做大量轉化的 IntroductionInterceptor 。在這里,我們繼承 org.spring framework.aop.support.DelegatingIntroductionInterceptor 實用類。我們可以直接實現 Introduction Interceptor 接口,但是,大多數情況下, DelegatingIntroductionInterceptor 是最合適的。

          DelegatingIntroductionInterceptor 的設計是將導入委托到真正實現導入接口的接口,隱藏完成這些工作的攔截器。委托可以使用構造方法參數設置到任何對象中,默認的委托就是自己(當無參數的構造方法被使用時)。這樣,在下面的例子里,委托是 DelegatingIntroduction Interceptor 的子類 LockMixin 。給定一個委托(默認是自身)的 DelegatingIntroductionIntercepto r 實例尋找被這個委托(而不是 IntroductionInterceptor )實現的所有接口,并支持它們中任何一個導入。子類如 LockMixin 也可能調用 suppressInterflace (Class intf) 方法隱藏不應暴露的接口。然而,不管 IntroductionInterceptor 準備支持多少接口, IntroductionAdvisor 將控制哪個接口將被實際暴露。一個導入的接口將隱藏目標的同一個接口的所有實現。

          這樣, LockMixin 繼承 DelegatingIntroductionInterceptor 并自己實現 Lockable 。父類自動選擇支持導入的 Lockable ,所以我們不需要指定它。用這種方法我們可以導入任意數量的接口。

          注意 locked 實例變量的使用,這有效地添加了額外的狀態到目標對象。

          public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {

          ??? private boolean locked;

          ??? public void lock() {
          ?????? this.locked = true;
          ??? }
          ??? public void unlock() {
          ??????? this.locked = false;
          ??? }

          ??? public boolean locked() {
          ??????? return this.locked;
          ??? }

          ??? public Object invoke(MethodInvocation invocation) throws Throwable {
          ?????? if (locked() && invocation.getMethod().getName().indexOf("set") == 0)
          ??????????? throw new LockedException();

          ??????? return super.invoke(invocation); ???
          ???}
          }

          通常不要需要改寫 invoke() 方法,實現 DelegatingIntroductionInterceptor 就足夠了,如果是導入的方法, DelegatingIntroductionInterceptor 實現會調用委托方法,否則繼續沿著連接點處理。在現在的情況下,我們需要添加一個檢查:在上鎖狀態下不能調用 setter 方法。

          所需的導入 advisor 是很簡單的。只有保存一個獨立的 LockMixin 實例,并指定導入的接口,在這里就是 Lockable 。一個稍微復雜一點例子可能需要一個導入攔截器(可以定義成 prototype )的引用:在這種情況下, LockMixin 沒有相關配置,所以我們簡單地使用 new 來創建它。

          public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

          ??? public LockMixinAdvisor() {
          ??????? super(new LockMixin(), Lockable.class);
          ??? }
          }

          我們可以非常簡單地使用這個 advisor ,它不需要任何配置(但是,有一點必要的,就是不可能在沒有 IntroductionAdvisor 的情況下使用 IntroductionInterceptor )。和導入一樣,通常 advisor 必須是針對每個實例的,并且是有狀態的。我們會有不同的 LockMixinAdvisor 每個被通知對象,會有不同的 LockMixin 。 advisor 組成了被通知對象的狀態的一部分。

          和其他 advisor 一樣,我們可以使用 Advised.addAdvisor() 方法以編程地方式使用這種 advisor ,或者在 XML 中配置(推薦這種方式)。下面將討論所有代理創建,包括“自動代理創建者”,選擇代理創建以正確地處理導入和有狀態的混入。


          Spring 中,一個 advisor 就是一個 aspect 的完整的模塊化表示。一般地,一個 advisor 包括通知和切入點。

          撇開導入這種特殊情況,任何 advisor 可被用于任何通知。 org.springframework.aop. support.DefaultPointcutAdvisor 是最通用的 advisor 類。例如,它可以和 MethodInterceptor 、 BeforeAdvice ThrowsAdvice 一起使用。

          Spring 中可以將 advisor 和通知混合在一個 AOP 代理中。例如,你可以在一個代理配置中使用一個對 Around 通知、 Throws 通知和 Before 通知的攔截: Spring 將自動創建必要的攔截器鏈。


          三.用ProxyFactoryBean創建AOP代理


          如果你在為你的業務對象使用 Spring IoC 容器(例如, ApplicationContext BeanFactory ),你應該會或者你愿意會使用 Spring AOP FactoryBean (記住, factory bean 引入了一個間接層,它能創建不同類型的對象)。

          spring 中創建 AOP proxy 的基本途徑是使用 org.springframework.aop.framework. ProxyFactoryBean 。這樣可以對 pointcut advice 做精確控制。但是,如果你不需要這種控制,那些簡單的選擇可能更適合你。

          1 .基本概要

          ProxyFactoryBean 和其他 Spring FactoryBean 實現一樣,引入一個間接的層次。如果你定義一個名字為 foo ProxyFactoryBean ,引用 foo 的對象所看到的不是 ProxyFactoryBean 實例本身,而是由實現 ProxyFactoryBean 的類的 getObject() 方法所創建的對象。這個方法將創建一個包裝了目標對象的 AOP 代理。

          使用 ProxyFactoryBean 或者其他 IoC 可知的類來創建 AOP 代理的最重要的優點之一是 IoC 可以管理通知和切入點。這是一個非常的強大的功能,能夠實現其他 AOP 框架很難實現的特定的方法。例如,一個通知本身可以引用應用對象(除了目標對象,它在任何 AOP 框架中都可以引用應用對象),這完全得益于依賴注入所提供的可插入性。

          2 JavaBean 的屬性

          類似于 Spring 提供的絕大部分 FactoryBean 實現一樣, ProxyFactoryBean 也是一個 javabean ,我們可以利用它的屬性來指定你將要代理的目標,指定是否使用 CGLIB 。

          一些關鍵屬性來自 org.springframework.aop.framework.ProxyConfig ,它是所有 AOP 代理工廠的父類。這些關鍵屬性包括:

          ? proxyTargetClass :如果我們應該代理目標類,而不是接口,這個屬性的值為 true 。如果這是 true ,我們需要使用 CGLIB

          ? optimize :是否使用強優化來創建代理。不要使用這個設置,除非你了解相關的 AOP 代理是如何處理優化的。目前這只對 CGLIB 代理有效,對 JDK 動態代理無效(默認)。

          ? frozen :是否禁止通知的改變,一旦代理工廠已經配置。默認是 false 。

          ? exposeProxy :當前代理是否要暴露在 ThreadLocal 中,以便它可以被目標對象訪問(它可以通過 MethodInvocation 得到,不需要 ThreadLocal )。如果一個目標需要獲得它的代理,并且 exposeProxy 的值是 ture ,可以使用 AopContext.currentProxy() 方法。

          ? AopProxyFactory :所使用的 AopProxyFactory 具體實現。這個參數提供了一條途徑來定義是否使用動態代理、 CGLIB 還是其他代理策略。默認實現將適當地選擇動態代理或 CGLIB 。一般不需要使用這個屬性;它的意圖是允許 Spring 1.1 使用另外新的代理類型。

          其他 ProxyFactoryBean 特定的屬性包括:

          ? proxyInterfaces :接口名稱的字符串數組。如果這個沒有提供, CGLIB 代理將被用于目標類。

          ? interceptorNames: Advisor interceptor 或其他被應用的通知名稱的字符串數組。順序是很重要的。這里的名稱是當前工廠中 bean 的名稱,包括來自祖先工廠的 bean 的名稱。

          ? Singleton :工廠是否返回一個單獨的對象,無論 getObject() 被調用多少次。許多 FactoryBean 的實現提供這個方法,默認值是 true 。如果你想要使用有狀態的通知,(例如,用于有狀態的 mixin )將這個值設為 false ,使用 prototype 通知。

          3 .代理接口

          讓我們來看一個簡單的 ProxyFactoryBean 的實際例子。這個例子涉及到一個將被代理的目標 bean ,在這個例子里,這個 bean 被定義為“ personTarget ”;一個 advisor 和一個 interceptor 來提供 advice ;一個 AOP 代理 bean 定義,該 bean 指定目標對象(這里是 personTarget bean ),代理接口和使用的 advice 。

          <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. NopInterceptor"> </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>

          請注意, person bean interceptorNames 屬性提供一個 String 列表,列出的是該 ProxyFactoryBean 使用的,在當前 bean 工廠定義的 interceptor 或者 advisor 的名字( advisor interceptor 、 before after returning throws advice 對象皆可)。 Advisor 在該列表中的次序很重要。

          你也許會對該列表為什么不采用 bean 的引用存有疑問。原因就在于如果 ProxyFactory Bean singleton 屬性被設置為 false ,那么 bean 工廠必須能返回多個獨立的代理實例。如果有任何一個 advisor 本身是 prototype 的,那么它就需要返回獨立的實例,也就是有必要從 bean 工廠獲取 advisor 的不同實例, bean 的引用在這里顯然是不夠的。

          上面定義的“ person bean 定義可以作為 Person 接口的實現來使用,如下所示。

          Person person = (Person) factory.getBean("person");

          在同一個 IoC 的上下文中,其他的 bean 可以依賴于 Person 接口,就像依賴于一個普通的 java 對象一樣。

          <bean id="personUser" class="com.mycompany.PersonUser">
          ??? <property name="person"><ref local="person" /></property>
          </bean>

          在這個例子里, PersonUser 類暴露了一個類型為 Person 的屬性。只要是在用到該屬性的地方, AOP 代理都能透明地替代一個真實的 Person 實現。但是,這個類可能是一個動態代理類。也就是有可能把它類型轉換為一個 Advised 接口(該接口在下面的章節中論述)。

          4 .代理類

          如果你需要代理的是類,而不是一個或多個接口,又該怎么辦呢 ?

          象一下我們上面的例子,如果沒有 Person 接口,我們需要通知一個叫 Person 的類,而且該類沒有實現任何業務接口。在這種情況下,你可以配置 Spring 使用 CGLIB 代理,而不是動態 代理。你只要在上面的 ProxyFactoryBean 定義中把它的 proxyTargetClass 屬性改成 true 就可以。

          只要你愿意,即使在有接口的情況下,你也可以強迫 Spring 使用 CGLIB 代理。

          CGLIB 代理是通過在運行期產生目標類的子類來進行工作的。 Spring 可以配置這個生成的子類,來代理原始目標類的方法調用。這個子類是用 Decorator 設計模式置入到 advice 中的。

          CGLIB 代理對于用戶來說應該是透明的。然而,還有以下一些因素需要考慮。

          1 Final 方法不能被通知,因為不能被重寫。

          2 )你需要在你的 classpath 中包括 CGLIB 的二進制代碼,而動態代理對任何 JDK 都是可用的。

          3 CGLIB 和動態代理在性能上有微小的區別,對 Spring 1.0 來說,后者稍快。另外,以后可能會有變化。在這種情況下,性能不是決定性因素。

          posted on 2006-11-01 11:17 OMG 閱讀(1394) 評論(0)  編輯  收藏 所屬分類: Spring

          <2006年11月>
          2930311234
          567891011
          12131415161718
          19202122232425
          262728293012
          3456789

          常用鏈接

          留言簿(1)

          隨筆分類

          隨筆檔案

          IT風云人物

          文檔

          朋友

          相冊

          經典網站

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 广元市| 从江县| 新竹县| 万年县| 承德市| 虹口区| 绿春县| 山西省| 正定县| 泗阳县| 安岳县| 伊川县| 舟曲县| 山阴县| 青海省| 丰原市| 遂溪县| 伊川县| 时尚| 夏邑县| 和田市| 郁南县| 雷山县| 巴林右旗| 横山县| 襄垣县| 萍乡市| 彭州市| 苗栗市| 曲阳县| 太康县| 资中县| 都匀市| 宁南县| 循化| 迭部县| 灵台县| 福贡县| 石首市| 垦利县| 丰县|