Spring3開發實戰 之 第三章:AOP開發(2) – JAVA編程語言程序開發技術文章

啟用@AspectJ支持
通過在你的Spring的配置中引入下列元素來啟用Spring對@AspectJ的支持:
<aop:aspectj-autoproxy/>
聲明一個方面
在application context中定義的任意帶有一個@Aspect切面(擁有@Aspect註解)的bean都將被Spring自動識別並用於配置在Spring AOP。
配置如:

java代碼:
查看復制到剪貼板打印
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect"> 
   <!– configure properties of aspect here as normal –> 
</bean> 
Java類: 
@Aspect 
public class NotVeryUsefulAspect { 

聲明一個切入點(pointcut)
使用 @Pointcut 註解來表示 ,示例如下:

java代碼:
查看復制到剪貼板打印
@Pointcut("execution(* transfer(..))") 
private void anyOldTransfer() {} 
切入點指定者的支持
Spring AOP 支持在切入點表達式中使用如下的AspectJ切入點指定者:
1:execution:匹配方法執行的連接點,這是你將會用到的Spring的最主要的切入點指定者。
2:within:限定匹配特定類型的連接點(在使用Spring AOP的時候,在匹配的類型中定義的方法的執行)。
3:this:限定匹配特定的連接點(使用Spring AOP的時候方法的執行),其中bean reference(Spring AOP 代理)是指定類型的實例。
4: target:限定匹配特定的連接點(使用Spring AOP的時候方法的執行),其中目標對象(被代理的appolication object)是指定類型的實例。
5: args:限定匹配特定的連接點(使用Spring AOP的時候方法的執行),其中參數是指定類型的實例。
6: @target:限定匹配特定的連接點(使用Spring AOP的時候方法的執行),其中執行的對象的類已經有指定類型的註解。
7: @args:限定匹配特定的連接點(使用Spring AOP的時候方法的執行),其中實際傳入參數的運行時類型有指定類型的註解。
8: @within:限定匹配特定的連接點,其中連接點所在類型已指定註解(在使用Spring AOP的時候,所執行的方法所在類型已指定註解)。
9: @annotation:限定匹配特定的連接點(使用Spring AOP的時候方法的執行),其中連接點的主題有某種給定的註解
合並切入點表達式
切入點表達式可以使用‘&&', '||' 和 '!'來合並.還可以通過名字來指向切入點表達式。
切入點表達式的基本語法
Spring AOP 用戶可能會經常使用 execution pointcut designator。執行表達式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
除瞭返回類型模式(上面代碼片斷中的ret-type-pattern),名字模式和參數模式以外,所有的部分都是可選的。 返回類型模式決定瞭方法的返回類型必須依次匹配一個連接點。
類型匹配模式
1:*:匹配任何數量字符;比如模式 (*,String) 匹配瞭一個接受兩個參數的方法,第一個可以是任意類型,第二個則必須是String類型
2:..:匹配任何數量字符的重復,如在類型模式中匹配任何數量子包;而在方法參數模式中匹配任何數量參數,可以使零到多個。
3: +:匹配指定類型的子類型;僅能作為後綴放在類型模式後邊。
類型匹配模式示例
1:java.lang.String    匹配String類型;
2:java.*.String       匹配java包下的任何“一級子包”下的String類型;
                       如匹配java.lang.String,但不匹配java.lang.ss.String
3:java..*             匹配java包及任何子包下的任何類型;
                       如匹配java.lang.String、java.lang.annotation.Annotation
4:java.lang.*ing      匹配任何java.lang包下的以ing結尾的類型;
5:java.lang.Number+   匹配java.lang包下的任何Number的子類型;
                       如匹配java.lang.Integer,也匹配java.math.BigInteger
切入點表達式的基本示例,使用execution
1:public * *(..)
任何公共方法的執行
2:* cn.javass..IPointcutService.*() 
cn.javass包及所有子包下IPointcutService接口中的任何無參方法
3:* cn.javass..*.*(..)
cn.javass包及所有子包下任何類的任何方法
4:* cn.javass..IPointcutService.*(*)
cn.javass包及所有子包下IPointcutService接口的任何隻有一個參數方法
5:* (!cn.javass..IPointcutService+).*(..)
非“cn.javass包及所有子包下IPointcutService接口及子類型”的任何方法
6:* cn.javass..IPointcutService+.*()
cn.javass包及所有子包下IPointcutService接口及子類型的的任何無參方法
7:* cn.javass..IPointcut*.test*(java.util.Date)
cn.javass包及所有子包下IPointcut前綴類型的的以test開頭的隻有一個參數類型為java.util.Date的方法,註意該匹配是根據方法簽名的參數類型進行匹配的,而不是根據執行時傳入的參數類型決定的如定義方法:public void test(Object obj);即使執行時傳入java.util.Date,也不會匹配的。
8:* cn.javass..IPointcut*.test*(..)  throws IllegalArgumentException, ArrayIndexOutOfBoundsException
cn.javass包及所有子包下IPointcut前綴類型的的任何方法,且拋出IllegalArgumentException和ArrayIndexOutOfBoundsException異常
9:* (cn.javass..IPointcutService+ && java.io.Serializable+).*(..)
任何實現瞭cn.javass包及所有子包下IPointcutService接口和java.io.Serializable接口的類型的任何方法
10:@java.lang.Deprecated * *(..)
任何持有@java.lang.Deprecated註解的方法
11:@java.lang.Deprecated @cn.javass..Secure  * *(..)
任何持有@java.lang.Deprecated和@cn.javass..Secure註解的方法
12:@(java.lang.Deprecated || cn.javass..Secure) * *(..)
任何持有@java.lang.Deprecated或@ cn.javass..Secure註解的方法
13:(@cn.javass..Secure  *)  *(..)
任何返回值類型持有@cn.javass..Secure的方法
14:*  (@cn.javass..Secure *).*(..)
任何定義方法的類型持有@cn.javass..Secure的方法
15:* *(@cn.javass..Secure (*) , @cn.javass..Secure (*))
任何簽名帶有兩個參數的方法,且這個兩個參數都被@ Secure標記瞭,如public void test(@Secure String str1, @Secure String str1);
16:* *((@ cn.javass..Secure *))或* *(@ cn.javass..Secure *)
任何帶有一個參數的方法,且該參數類型持有@ cn.javass..Secure;如public void test(Model model);且Model類上持有@Secure註解
17:* *(@cn.javass..Secure (@cn.javass..Secure *) ,@ cn.javass..Secure (@cn.javass..Secure *))
任何帶有兩個參數的方法,且這兩個參數都被@ cn.javass..Secure標記瞭;且這兩個參數的類型上都持有@ cn.javass..Secure;
18:* *(java.util.Map<cn.javass..Model, cn.javass..Model>, ..)
任何帶有一個java.util.Map參數的方法,且該參數類型是以<cn.javass..Model, cn.javass..Model>為泛型參數;註意隻匹配第一個參數為java.util.Map,不包括子類型;如public void test(HashMap<Model, Model> map, String str);將不匹配,必須使用“* *(java.util.HashMap<cn.javass..Model,cn.javass..Model>, ..)”進行匹配;而public void test(Map map, int i);也將不匹配,因為泛型參數不匹配
19:* *(java.util.Collection<@cn.javass..Secure *>)
任何帶有一個參數(類型為java.util.Collection)的方法,且該參數類型是有一個泛型參數,該泛型參數類型上持有@cn.javass..Secure註解;如public void test(Collection<Model> collection);Model類型上持有@cn.javass..Secure
切入點表達式的基本示例,使用within匹配指定類型內的方法
1:within(cn.javass..*)
cn.javass包及子包下的任何方法執行
2:within(cn.javass..IPointcutService+)
cn.javass包或所有子包下IPointcutService類型及子類型的任何方法
3:within(@cn.javass..Secure *)
持有cn.javass..Secure註解的任何類型的任何方法必須是在目標對象上聲明這個註解,在接口上聲明的對它不起作用
 
切入點表達式的基本示例,使用this
使用“this(類型全限定名)”匹配當前AOP代理對象類型的執行方法;註意是AOP代理對象的類型匹配,這樣就可能包括引入接口方法也可以匹配;註意this中使用的表達式必須是類型全限定名,不支持通配符
1:this(cn.javass.spring.chapter6.service.IPointcutService)
當前AOP對象實現瞭 IPointcutService接口的任何方法
2:this(cn.javass.spring.chapter6.service.IIntroductionService)
當前AOP對象實現瞭 IIntroductionService接口的任何方法也可能是引入接口
切入點表達式的基本示例,使用target
使用“target(類型全限定名)”匹配當前目標對象類型的執行方法;註意是目標對象的類型匹配,這樣就不包括引入接口也類型匹配;註意target中使用的表達式必須是類型全限定名,不支持通配符
1:target(cn.javass.spring.chapter6.service.IPointcutService)
當前目標對象(非AOP對象)實現瞭 IPointcutService接口的任何方法
2:target(cn.javass.spring.chapter6.service.IIntroductionService)
當前目標對象(非AOP對象) 實現瞭IIntroductionService 接口的任何方法不可能是引入接口
切入點表達式的基本示例,使用args
使用“args(參數類型列表)”匹配當前執行的方法傳入的參數為指定類型的執行方法;註意是匹配傳入的參數類型,不是匹配方法簽名的參數類型;參數類型列表中的參數必須是類型全限定名,通配符不支持;args屬於動態切入點,這種切入點開銷非常大,非特殊情況最好不要使用
1:args (java.io.Serializable,..)
任何一個以接受“傳入參數類型為 java.io.Serializable” 開頭,且其後可跟任意個任意類型的參數的方法執行,args指定的參數類型是在運行時動態匹配的
切入點表達式的基本示例,使用@within
使用“@within(註解類型)”匹配所以持有指定註解類型內的方法;註解類型也必須是全限定類型名
1:@within cn.javass.spring.chapter6.Secure)
任何目標對象對應的類型持有Secure註解的類方法;必須是在目標對象上聲明這個註解,在接口上聲明的對它不起作用
切入點表達式的基本示例,使用@target
使用“@target(註解類型)”匹配當前目標對象類型的執行方法,其中目標對象持有指定的註解;註解類型也必須是全限定類型名
1:@target (cn.javass.spring.chapter6.Secure)
任何目標對象持有Secure註解的類方法;必須是在目標對象上聲明這個註解,在接口上聲明的對它不起作用
切入點表達式的基本示例,使用@args
使用“@args(註解列表)”匹配當前執行的方法傳入的參數持有指定註解的執行;註解類型也必須是全限定類型名
1:@args (cn.javass.spring.chapter6.Secure)
任何一個隻接受一個參數的方法,且方法運行時傳入的參數持有註解 cn.javass.spring.chapter6.Secure;動態切入點,類似於arg指示符;
切入點表達式的基本示例,使用@annotation
使用“@annotation(註解類型)”匹配當前執行方法持有指定註解的方法;註解類型也必須是全限定類型名
1:@annotation(cn.javass.spring.chapter6.Secure )
當前執行方法上持有註解 cn.javass.spring.chapter6.Secure將被匹配
切入點表達式的基本示例,使用bean
使用“bean(Bean id或名字通配符)”匹配特定名稱的Bean對象的執行方法;Spring AOP擴展的,在AspectJ中無相應概念
1:bean(*Service)
匹配所有以Service命名(id或name)結尾的Bean
切入點表達式的基本示例,使用reference pointcut
引用其他命名切入點,隻有@ApectJ風格支持,Schema風格不支持,如下所示:

java代碼:
查看復制到剪貼板打印
@Pointcut(value="bean(*Service)") //命名切入點1 
private void pointcut1(){} 
@Pointcut(value="@args(cn.javass.spring.chapter6.Secure)") //命名切入點2 
private void pointcut2(){} 
  
@Before(value = "pointcut1() && pointcut2()")     //引用命名切入點 
public void referencePointcutTest1(JoinPoint jp) { 
    dump("pointcut1() && pointcut2()", jp); 

聲明通知
通知是跟一個切入點表達式關聯起來的,並且在切入點匹配的方法執行之前或者之後或者之前和之後運行。 切入點表達式可能是指向已命名的切入點的簡單引用或者是一個已經聲明過的切入點表達式。
前置通知(Before advice) ,使用 @Before 註解聲明

java代碼:
查看復制到剪貼板打印
@Aspect 
public class BeforeExample { 
  @Before("execution(* com.xyz.myapp.dao.*.*(..))") 
  public void doAccessCheck() { 
// … 
  } 

返回後通知(After returning advice) ,使用 @AfterReturning註解聲明
後通知(After (finally) advice) ,使用 @After註解聲明
拋出後通知(After throwing advice) ,使用 @AfterThrowing註解聲明
可以將拋出的異常綁定到通知的一個參數上 ,如下:

java代碼:
查看復制到剪貼板打印
@AfterThrowing( 
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", 
throwing="ex") 
  public void doRecoveryActions(Exception ex) { 
// … 
  } 
環繞通知(Around Advice) ,使用 @Around註解聲明

java代碼:
查看復制到剪貼板打印
@Around("com.xyz.myapp.SystemArchitecture.businessService()") 
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { 
// start stopwatch 
Object retVal = pjp.proceed(); 
// stop stopwatch 
return retVal; 

給Advice傳遞參數
通常情況下,Advice都需要獲取一定的參數,比如:Before需要截獲傳入的參數,而After需要獲取方法的返回值等等。下面介紹兩種方式
方式一:使用JoinPoint來給Advice的方法傳遞參數
Spring AOP提供使用org.aspectj.lang.JoinPoint類型獲取連接點數據,任何通知方法的第一個參數都可以是JoinPoint(環繞通知是ProceedingJoinPoint,JoinPoint子類),當然第一個參數位置也可以是JoinPoint.StaticPart類型,這個隻返回連接點的靜態部分。
1:JoinPoint:提供訪問當前被通知方法的目標對象、代理對象、方法參數等數據

java代碼:
查看復制到剪貼板打印
public interface JoinPoint { 
    String toString();         //連接點所在位置的相關信息 
    String toShortString();     //連接點所在位置的簡短相關信息 
    String toLongString();     //連接點所在位置的全部相關信息 
    Object getThis();         //返回AOP代理對象 
    Object getTarget();       //返回目標對象 
    Object[] getArgs();       //返回被通知方法參數列表 
    Signature getSignature();  //返回當前連接點簽名 
    SourceLocation getSourceLocation();//返回連接點方法所在類文件中的位置 
    String getKind();        //連接點類型 
    StaticPart getStaticPart(); //返回連接點靜態部分 

2:ProceedingJoinPoint:用於環繞通知,使用proceed()方法來執行目標方法

java代碼:
查看復制到剪貼板打印
public interface ProceedingJoinPoint extends JoinPoint { 
    public Object proceed() throws Throwable; 
    public Object proceed(Object[] args) throws Throwable; 

3:JoinPoint.StaticPart:提供訪問連接點的靜態部分,如被通知方法簽名、連接點類型等

java代碼:
查看復制到剪貼板打印
public interface StaticPart { 
Signature getSignature();    //返回當前連接點簽名 
String getKind();          //連接點類型 
    int getId();               //唯一標識 
String toString();         //連接點所在位置的相關信息 
    String toShortString();     //連接點所在位置的簡短相關信息 
    String toLongString();     //連接點所在位置的全部相關信息 

4:示例:使用如下方式在通知方法上聲明,必須是在第一個參數,然後使用jp.getArgs()就能獲取到被通知方法參數:

java代碼:
查看復制到剪貼板打印
@Before(value="execution(* sayBefore(*))") 
public void before(JoinPoint jp) {} 
@Before(value="execution(* sayBefore(*))") 
public void before(JoinPoint.StaticPart jp) {} 
方式二:使用args來給Advice的方法傳遞參數,示例如:

java代碼:
查看復制到剪貼板打印
@Before(value="execution(* test(*)) && args(param)", argNames="param") 
public void before1(String param) { 
    System.out.println("===param:" + param); 

切入點表達式execution(* test(*)) && args(param) :
(1)首先execution(* test(*))匹配任何方法名為test,且有一個任何類型的參數;
(2)args(param)將首先查找通知方法上同名的參數,並在方法執行時(運行時)匹配傳入的參數是使用該同名參數類型,即java.lang.String;如果匹配將把該被通知參數傳遞給通知方法上同名參數。
參數匹配的策略
1:可以通過“argNames”屬性指定參數名 ,示例如:

java代碼:
查看復制到剪貼板打印
@Before(value=" args(param)", argNames="param") //明確指定瞭 
public void before1(String param) { 
    System.out.println("===param:" + param); 

2:如果第一個參數類型是JoinPoint、ProceedingJoinPoint或JoinPoint.StaticPart類型,應該從“argNames”屬性省略掉該參數名(可選,寫上也對),這些類型對象會自動傳入的,但必須作為第一個參數 。示例如:

java代碼:
查看復制到剪貼板打印
@Before(value=" args(param)", argNames="param") //明確指定瞭 
public void before1(JoinPoint jp, String param) { 
    System.out.println("===param:" + param); 

3:如果“ class文件中含有變量調試信息”,Spring將可以使用這些方法簽名中的參數名來確定參數名,示例如下:

java代碼:
查看復制到剪貼板打印
@Before(value=" args(param)") //不需要argNames瞭 
public void before1(JoinPoint jp, String param) { 
    System.out.println("===param:" + param); 

4:如果沒有“ class文件中含有變量調試信息”,將嘗試自己的參數匹配算法,如果發現參數綁定有二義性將拋出AmbiguousBindingException異常;對於隻有一個綁定變量的切入點表達式,而通知方法隻接受一個參數,說明綁定參數是明確的,從而能配對成功 ,示例如下:
@Before(value=" args(param)")
public void before1(JoinPoint jp, String param) {
    System.out.println("===param:" + param);
}
5:以上策略失敗將拋出IllegalArgumentException。
處理參數

java代碼:
查看復制到剪貼板打印
@Around("execution(List<Account> find*(..)) &&" + 
"com.xyz.SystemArchitecture.inDataAccessLayer() && " + "args(accountHolderNamePattern)") 
public Object preProcessQueryPattern(ProceedingJoinPoint pjp, String accountHolderNamePattern) 
throws Throwable { 
  String newPattern = preProcess(accountHolderNamePattern); 
  return pjp.proceed(new Object[] {newPattern}); 

獲取方法運行後的返回值

java代碼:
查看復制到剪貼板打印
@AfterReturning( 
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", 
returning="retVal") 
  public void doAccessCheck(Object retVal) { 
// … 
  } 
Advice執行的順序
如果有多個通知想要在同一連接點運行會發生什麼?Spring AOP 的執行通知的順序跟AspectJ的一樣。 在“進入”連接點的情況下,最高優先級的通知會先執行(所以上面給出的兩個前置通知(before advice)中,優先級高的那個會先執行)。 在“退出”連接點的情況下,最高優先級的通知會最後執行。隻需要記住通知是按照定義的順序來執行的就可以瞭。
當定義在 不同的 切面裡的兩個通知都需要在一個相同的連接點中運行,那麼除非你指定,否則執行的順序是未知的。 你可以通過指定優先級來控制執行順序。在Spring中可以在切面類中實現 org.springframework.core.Ordered 接口做到這一點。 在兩個切面中,Ordered.getValue() 方法返回值較低的那個有更高的優先級。

java代碼:
查看復制到剪貼板打印
聲明一個切面 :<aop:aspect> 
<aop:config> 
  <aop:aspect id="myAspect" ref="adviceBean"> 
… 
    </aop:aspect> 
</aop:config> 
<bean id="adviceBean" class="…"> 
… 
</bean> 
聲明一個切入點 :<aop:pointcut> 
<aop:config> 
  <aop:pointcut id="businessService" 
expression="execution(* com.xyz.myapp.service.*.*(..))"/> 
</aop:config> 
 
在切面裡面聲明一個切入點和聲明一個頂級的切入點非常類似:

java代碼:
查看復制到剪貼板打印
<aop:config> 
  <aop:aspect id="myAspect" ref="aBean"> 
<aop:pointcut id="businessService"   
expression="execution(* com.xyz.myapp.service.*.*(..))"/> 
… 
  </aop:aspect> 
</aop:config> 
當需要連接子表達式的時候,'&'在XML中用起來非常不方便,所以關鍵字'and', 'or' 和 'not'可以分別用來代替'&', '||' 和 '!'。

java代碼:
查看復制到剪貼板打印
聲明通知Advice,Before  Advice 
<aop:aspect id="beforeExample" ref="aBean"> 
<aop:before 
  pointcut="execution(* com.xyz.myapp.dao.*.*(..))" 
  method="doAccessCheck"/> 
… 
</aop:aspect> 
返回後通知(After returning advice) 
<aop:aspect id="afterReturningExample" ref="aBean"> 
<aop:after-returning 
  pointcut-ref="dataAccessOperation" 
  method="doAccessCheck"/> 
… 
</aop:aspect> 
  

java代碼:
查看復制到剪貼板打印
拋出異常後通知(After throwing advice) 
<aop:aspect id="afterThrowingExample" ref="aBean"> 
<aop:after-throwing 
  pointcut-ref="dataAccessOperation" 
  thowing="dataAccessEx" 
  method="doRecoveryActions"/> 
… 
</aop:aspect> 
後通知(After (finally) advice) 
<aop:aspect id="afterFinallyExample" ref="aBean"> 
<aop:after 
  pointcut-ref="dataAccessOperation" 
  method="doReleaseLock"/> 
… 
</aop:aspect> 
 
Around通知

java代碼:
查看復制到剪貼板打印
<aop:aspect id="aroundExample" ref="aBean"> 
<aop:around 
  pointcut-ref="businessService" 
  method="doBasicProfiling"/> 
… 
</aop:aspect> 
通知的參數,可以通過 arg-names 屬性來實現

java代碼:
查看復制到剪貼板打印
<aop:before 
  pointcut="com.xyz.lib.Pointcuts.anyPublicMethod()" 
  method="audit" 
  arg-names="auditable"/> 
Advisor 
“advisors”這個概念來自Spring1.2對AOP的支持,在AspectJ中是沒有等價的概念。 advisor就像一個小的自包含的切面,這個切面隻有一個通知。

java代碼:
查看復制到剪貼板打印
<aop:config> 
  <aop:pointcut id="businessService" 
expression="execution(* com.xyz.myapp.service.*.*(..))"/> 
  <aop:advisor 
  pointcut-ref="businessService" 
  advice-ref="tx-advice"/> 
</aop:config> 
Spring AOP還是完全用AspectJ?
優先選用Spring的AOP,因為它不是一個人在戰鬥,Spring除瞭AOP,還有IOC,還有事務等其他功能。
如果你需要通知domain對象或其它沒有在Spring容器中 管理的任意對象,那麼你需要使用AspectJ。
Spring AOP中使用@AspectJ還是XML?
優先使用@AspectJ。
XML風格有兩個缺點。第一是它不能完全將需求實現的地方封裝到一個位置。DRY原則中說系統中的每一項知識都必須具有單一、無歧義、權威的表示。 當使用XML風格時,如何實現一個需求的知識被分割到支撐類的聲明中以及XML配置文件中。當使用@AspectJ風格時就隻有一個單獨的模塊 -切面- 信息被封裝瞭起來。 第二是XML風格同@AspectJ風格所能表達的內容相比有更多的限制:僅僅支持"singleton"切面實例模型,並且不能在XML中組合命名連接點的聲明。
 
Spring AOP使用JDK動態代理或者CGLIB來為目標對象創建代理。如果被代理的目標對象實現瞭至少一個接口,則會使用JDK動態代理。所有該目標類型實現的接口都將被代理。若該目標對象沒有實現任何接口,則創建一個CGLIB代理。
如果你希望強制使用CGLIB代理,(例如:希望代理目標對象的所有方法,而不隻是實現自接口的方法)那也可以。但是需要考慮以下問題:
1:無法通知(advise)Final 方法,因為他們不能被覆寫。
2:將CGLIB 2二進制發行包放在classpath下面,JDK本身就提供瞭動態代理
3:強制使用CGLIB代理需要將 <aop:config> 的 proxy-target-class 屬性設為true:
<aop:config proxy-target-class="true"></aop:config>
4:當需要使用CGLIB代理和@AspectJ自動代理支持,請按照如下的方式設置 <aop:aspectj-autoproxy> 的 proxy-target-class 屬性:
<aop:aspectj-autoproxy proxy-target-class="true"/>
切入點的API
org.springframework.aop.Pointcut 是最核心的接口,用來將 通知應用於特定的類和方法,完整的接口定義如下:

java代碼:
查看復制到剪貼板打印
public interface Pointcut { 
    ClassFilter getClassFilter(); 
    MethodMatcher getMethodMatcher(); 

1:將Pointcut接口分割成有利於重用類和方法匹配的兩部分,以及進行更細粒度的操作組合(例如與另一個方法匹配實現進行“或操作”)。
2:ClassFilter接口用來將切入點限定在一個給定的類集合中。如果matches()方法總是返回true,所有目標類都將被匹配:
public interface ClassFilter {
    boolean matches(Class clazz);
}
3:MethodMatcher接口通常更重要,完整的接口定義如下:

java代碼:
查看復制到剪貼板打印
public interface MethodMatcher { 
    boolean matches(Method m, Class targetClass); 
    boolean isRuntime(); 
    boolean matches(Method m, Class targetClass, Object[] args); 

matches(Method, Class)方法用來測試這個切入點是否匹配目標類的指定方法。這將在AOP代理被創建的時候執行,這樣可以避免在每次方法調用的時候都執行。如果matches(Method, Class )對於一個給定的方法返回true,並且isRuntime() 也返回true,那麼matches(Method, Class , Object[])將在每個方法調用的時候被調用。這使得切入點在通知將被執行前可以查看傳入到方法的參數。
 
大多數MethodMatcher是靜態的,這意味著isRuntime()方法返回 false。在這種情況下,matches(Method, Class , Object[])永遠不會被調用。
 
靜態切入點
靜態切入點基於方法和目標類進行切入點判斷而不考慮方法的參數。在多數情況下,靜態切入點是高效的、最好的選擇。 Spring隻在第一次調用方法時執行靜態切入點:以後每次調用這個方法時就不需要再執行。
1:正則表達式切入點
  正則表達式是最常用的描述靜態切入點的方式,多數AOP框架都支持這種方式。org.springframework.aop.support.Perl5RegexpMethodPointcut是一個最基本的正則表達式切入點, 它使用Perl 5正則表達式語法。示例如下:

java代碼:
查看復制到剪貼板打印
<bean id="settersAndAbsquatulatePointcut" 
    class="org.springframework.aop.support.Perl5RegexpMethodPointcut"> 
    <property name="patterns"> 
        <list> 
            <value>.*set.*</value> 
            <value>.*absquatulate</value> 
        </list> 
    </property> 
</bean> 
攔截around通知
Spring裡使用方法攔截的around通知兼容AOP聯盟接口。實現around通知的MethodInterceptor應當實現下面的接口:

java代碼:
查看復制到剪貼板打印
public interface MethodInterceptor extends Interceptor { 
    Object invoke(MethodInvocation invocation) throws Throwable; 

提示:在invok方法裡面不要忘記調用:invocation.proceed();
前置通知

java代碼:
查看復制到剪貼板打印
public interface MethodBeforeAdvice extends BeforeAdvice { 
    void before(Method m, Object[] args, Object target) throws Throwable; 

後置通知

java代碼:
查看復制到剪貼板打印
public interface AfterReturningAdvice extends Advice { 
    void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable; 

異常通知
如果連接點拋出異常,異常通知(throws advice)將在連接點返回後被調用。 Spring提供類型檢查的異常通知,這意味著org.springframework.aop.ThrowsAdvice接口不包含任何方法:它隻是一個標記接口用來標識 所給對象實現瞭一個或者多個針對特定類型的異常通知方法。這些方法應當滿足下面的格式
afterThrowing([Method], [args], [target], subclassOfThrowable)
隻有最後一個參數是必須的。因此異常通知方法對方法及參數的需求,方法的簽名將從一到四個參數之間變化。 下面是一些throws通知的例子。
當一個RemoteException(包括它的子類)被拋出時,下面的通知會被調用:

java代碼:
查看復制到剪貼板打印
public class RemoteThrowsAdvice implements ThrowsAdvice { 
    public void afterThrowing(RemoteException ex) throws Throwable { 
        // Do something with remote exception 
    } 

引入通知

java代碼:
查看復制到剪貼板打印
public interface IntroductionInterceptor extends MethodInterceptor { 
    boolean implementsInterface(Class intf); 

使用ProxyFactoryBean創建AOP代理
使用ProxyFactoryBean或者其它IoC相關類帶來的最重要的好處之一就是創建AOP代理,這意味著通知和切入點也可以由IoC來管理。這是一個強大的功能並使得某些特定的解決方案成為可能
下面描述ProxyFactoryBean的屬性:
1:proxyTargetClass:這個屬性為true時,目標類本身被代理而不是目標類的接口。如果這個屬性值被設為true,CGLIB代理將被創建
2:optimize:用來控制通過CGLIB創建的代理是否使用激進的優化策略。除非完全瞭解AOP代理如何處理優化,否則不推薦用戶使用這個設置。目前這個屬性僅用於CGLIB代理;對於JDK動態代理(缺省代理)無效。
3:frozen:用來控制代理工廠被配置之後,是否還允許修改通知。缺省值為false(即在代理被配置之後,不允許修改代理的配置)。
4:exposeProxy:決定當前代理是否被保存在一個ThreadLocal中以便被目標對象訪問。(目標對象本身可以通過MethodInvocation來訪問,因此不需要ThreadLocal。) 如果個目標對象需要獲取代理而且exposeProxy屬性被設為true,目標對象可以使用AopContext.currentProxy()方法。
5:aopProxyFactory:使用AopProxyFactory的實現。這提供瞭一種方法來自定義是否使用動態代理,CGLIB或其它代理策略。 缺省實現將根據情況選擇動態代理或者CGLIB。一般情況下應該沒有使用這個屬性的需要;它是被設計來在Spring 1.1中添加新的代理類型的。
6:proxyInterfaces:需要代理的接口名的字符串數組。如果沒有提供,將為目標類使用一個CGLIB代理
7:interceptorNames:Advisor的字符串數組,可以包括攔截器或其它通知的名字。順序是很重要的,排在前面的將被優先服務。就是說列表裡的第一個攔截器將能夠第一個攔截調用。
這裡的名字是當前工廠中bean的名字,包括父工廠中bean的名字。這裡你不能使用bean的引用因為這會導致ProxyFactoryBean忽略通知的單例設置。
你可以把一個攔截器的名字加上一個星號作為後綴(*)。這將導致這個應用程序裡所有名字以星號之前部分開頭的advisor都被應用。
 
讓我們看一個關於ProxyFactoryBean的簡單例子,這個是對接口進行代理

java代碼:
查看復制到剪貼板打印
<bean id="personTarget" class="com.mycompany.PersonImpl"></bean> 
<bean id="myAdvisor" class="com.mycompany.MyAdvisor"></bean> 
<bean id="debugInterceptor" class="org. DebugInterceptor"></bean> 
<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean"> 
    <property name="proxyInterfaces"><value>com.Person</value></property> 
    <property name="target"><ref local="personTarget"/></property> 
    <property name="interceptorNames"> 
        <list> 
            <value>myAdvisor</value> 
            <value>debugInterceptor</value> 
        </list> 
    </property> 
</bean> 
如果要對類進行代理,隻需要把proxyTargetClass屬性設為true
定義如下的接口:

java代碼:
查看復制到剪貼板打印
public interface Api { 
public String t(int a); 

寫實現類如下:

java代碼:
查看復制到剪貼板打印
public class Impl implements Api{ 
public String t(int a) { 
System.out.println("now in impl a="+a); 
return a+" test"; 


寫一個Before的Advice實現

java代碼:
查看復制到剪貼板打印
public class MyBefore implements org.springframework.aop.MethodBeforeAdvice{ 
public void before(Method method, Object[] args, Object target) 
throws Throwable { 
System.out.println("現在調用的是:"+target.getClass().getName() 
+",方法是:"+method.getName() +",參數是:"+args[0]); 


定義如下的接口:

java代碼:
查看復制到剪貼板打印
public interface Api { 
public String t(int a); 

寫實現類如下:

java代碼:
查看復制到剪貼板打印
public class Impl implements Api{ 
public String t(int a) { 
System.out.println("now in impl a="+a); 
return a+" test"; 


寫一個Before的Advice實現

java代碼:
查看復制到剪貼板打印
public class MyBefore implements org.springframework.aop.MethodBeforeAdvice{ 
public void before(Method method, Object[] args, Object target) 
throws Throwable { 
System.out.println("現在調用的是:"+target.getClass().getName() 
+",方法是:"+method.getName() +",參數是:"+args[0]); 


定義如下的接口:

java代碼:
查看復制到剪貼板打印
public interface Api { 
public String t(int a); 

寫實現類如下:

java代碼:
查看復制到剪貼板打印
public class Impl implements Api{ 
public String t(int a) { 
System.out.println("now in impl a="+a); 
return a+" test"; 


寫一個Before的Advice實現

java代碼:
查看復制到剪貼板打印
public class MyBefore implements org.springframework.aop.MethodBeforeAdvice{ 
public void before(Method method, Object[] args, Object target) 
throws Throwable { 
System.out.println("現在調用的是:"+target.getClass().getName() 
+",方法是:"+method.getName() +",參數是:"+args[0]); 


配置文件如下:

java代碼:
查看復制到剪貼板打印
<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> 
<beans> 
<bean id="myTarget" class="cn.javass.Spring3.aop1.Impl"/> 
<bean id="myBefore" class="cn.javass.Spring3.aop1.MyBefore"/> 
<bean id="myIn" class="org.springframework.aop.framework.ProxyFactoryBean"> 
    <property name="proxyInterfaces"> 
<value>cn.javass.Spring3.aop1.Api</value> 
</property> 
<property name="target"><ref local="myTarget"/></property> 
    <property name="interceptorNames"> 
        <list> 
            <value>beforeAdvisor</value> 
        </list> 
    </property> 
</bean> 
<bean id="beforeAdvisor" 
    class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> 
    <property name="advice"> 
        <ref local="myBefore"/> 
    </property> 
    <property name="patterns"> 
        <list> 
            <value>.*</value> 
        </list> 
    </property> 
</bean> 
</beans> 
客戶端文件如下:

java代碼:
查看復制到剪貼板打印
public class Client { 
public static void main(String[] args) { 
ApplicationContext ctx = new ClassPathXmlApplicationContext( 
    new String[] {"applicationContext-1.xml"}); 
Api api = (Api)ctx.getBean("myIn"); 
String s = api.t(123); 
System.out.println("sssss="+s); 


AOP與設計模式
AOP實際是設計模式的一種擴展,設計模式所追求的是降低代碼之間的耦合度,增加程序的靈活性和可重用性,AOP實際上就是設計模式所追求的目標的一種實現。
所謂的分離關註就是將某一通用的需求功能從不相關的類之中分離出來;同時,能夠使得很多類共享一個行為,一旦行為發生變化,不必修改很多類 ,隻要修改這個行為就可以,從而達到更好的可擴展性和復用性。
AOP包括三個清晰的開發步驟:
1: 功能橫切:分解需求提取出橫切關註點。
 
2: 實現分離:各自獨立的實現這些關註點所需要完成的功能。
 
3: 功能回貼:在這一步裡,方面集成器通過創建一個模塊單元——方面來指定重組的規則。重組過程——也叫織入或結合——則使用這些信息來構建最終系統。
AOP與OOP開發的不同關鍵在於它處理橫切關註點的方式,在AOP中,每個關註點的實現都不知道其它關註點是否會‘關註’它,如信用卡處理模塊並不知道其它的關註點實現正在為它做日志和驗證操作。
應該攔截字段嗎?
我們在實現AOP的時候,想要修改某個屬性。者通常不是一種好的做法,因為屬性通常應該在類的內部進行訪問,他體現瞭類的封裝性,如果攔截字段並修改它,那麼就會破壞這種封裝性。
而攔截方法不會破壞封裝性,因為這些方法本身就是能夠被外界訪問的。  如果確實需要攔截字段,通常的做法是對這個字段定義getXXX和setXXX方法,然後攔截方法,進行相應的操作。
太多的方面?
也許你會認為AOP實在是太強大瞭,可以到處使用,從而設計出太多的方面,以至於當一個方法被調用時,都難以說清楚究竟執行瞭哪些代碼。
這是很可怕的,“過猶不及”,這是典型的過度設計的毛病。通常情況下,很少有一個對象實例需要5個方面以上。況且過多的方面會影響性能。
在設計的時候,通常如下情況會設計成方面:
(1)大部分模塊都需要使用的通用功能,包括系統級或模塊級的功能
(2)預計目前的實現,在今後進行功能擴展的可能性很大的地方

 
正交性
如果多個方面互相影響,造成一些無法預測的結果,該怎麼辦?多個切入點的功能實現疊加起來,甚至造成錯誤?
對於這種情況,在設計AOP的時候要特別註意,要遵循連接點的正交模型:不同種類的連接點和不同種類的實現應該能夠以任何順序組合使用。
換句話說,請保持你設計的方面的獨立性,功能實現的獨立性,不依賴於多個方面的執行先後順序。
對粗粒度對象使用AOP
AOP通常用來對粗粒度的對象進行功能增強,比如對業務邏輯對象,攔截某個業務方法,進行功能增強,從而提高系統的可擴展性。
註意不要在細粒度的對象上使用AOP,比如對某個實體描述對象。在運行時,這種細粒度對象通常實例很多,比如可能有多條數據,這種情況下,使用AOP,會有大量的方法攔截帶來的反射處理,嚴重影響性能。
作者:jinnianshilongnian

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。