1. 程式人生 > >Spring中的AOP(五)——在Advice方法中獲取目標方法的引數

Spring中的AOP(五)——在Advice方法中獲取目標方法的引數

獲取目標方法的資訊

    訪問目標方法最簡單的做法是定義增強處理方法時,將第一個引數定義為JoinPoint型別,當該增強處理方法被呼叫時,該JoinPoint引數就代表了織入增強處理的連線點。JoinPoint裡包含了如下幾個常用的方法:

  • Object[] getArgs:返回目標方法的引數

  • Signature getSignature:返回目標方法的簽名

  • Object getTarget:返回被織入增強處理的目標物件

  • Object getThis:返回AOP框架為目標物件生成的代理物件

    注意:當使用@Around處理時,我們需要將第一個引數定義為ProceedingJoinPoint型別,該類是JoinPoint的子類。

    下面的切面類(依然放在com.abc.advice包中)中定義了Before、Around、AfterReturning和After 4中增強處理,並分別在4種增強處理中訪問被織入增強處理的目標方法、目標方法的引數和被織入增強處理的目標物件等:

package com.abc.advice;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import
 org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public classAdviceTest{     @Around("execution(* com.abc.service.*.many*(..))")     public Object process(ProceedingJoinPoint point) throws Throwable {         System.out
.println("@Around:執行目標方法之前...");         //訪問目標方法的引數:         Object[] args = point.getArgs();         if (args != null && args.length > 0 && args[0].getClass() == String.class) {             args[0] = "改變後的引數1";         }         //用改變後的引數執行目標方法         Object returnValue = point.proceed(args);         System.out.println("@Around:執行目標方法之後...");         System.out.println("@Around:被織入的目標物件為:" + point.getTarget());         return "原返回值:" + returnValue + ",這是返回結果的字尾";     }          @Before("execution(* com.abc.service.*.many*(..))")     public void permissionCheck(JoinPoint point) {         System.out.println("@Before:模擬許可權檢查...");         System.out.println("@Before:目標方法為:" +                  point.getSignature().getDeclaringTypeName() +                  "." + point.getSignature().getName());         System.out.println("@Before:引數為:" + Arrays.toString(point.getArgs()));         System.out.println("@Before:被織入的目標物件為:" + point.getTarget());     }          @AfterReturning(pointcut="execution(* com.abc.service.*.many*(..))",          returning="returnValue")     public void log(JoinPoint point, Object returnValue) {         System.out.println("@AfterReturning:模擬日誌記錄功能...");         System.out.println("@AfterReturning:目標方法為:" +                  point.getSignature().getDeclaringTypeName() +                  "." + point.getSignature().getName());         System.out.println("@AfterReturning:引數為:" +                  Arrays.toString(point.getArgs()));         System.out.println("@AfterReturning:返回值為:" + returnValue);         System.out.println("@AfterReturning:被織入的目標物件為:" + point.getTarget());              }          @After("execution(* com.abc.service.*.many*(..))")     public void releaseResource(JoinPoint point) {         System.out.println("@After:模擬釋放資源...");         System.out.println("@After:目標方法為:" +                  point.getSignature().getDeclaringTypeName() +                  "." + point.getSignature().getName());         System.out.println("@After:引數為:" + Arrays.toString(point.getArgs()));         System.out.println("@After:被織入的目標物件為:" + point.getTarget());     } }

    在AdviceManager類中增加以下內容:

//將被AdviceTest的各種方法匹配
public String manyAdvices(String param1, String param2) {
    System.out.println("方法:manyAdvices");
    return param1 + " 、" + param2;
}

    在com.abc.main.AOPTest中加入方法的呼叫,觸發切點:

String result = manager.manyAdvices("aa""bb");
System.out.println("Test方法中呼叫切點方法的返回值:" + result);

    下面是執行結果:

@Around:執行目標方法之前...
@Before:模擬許可權檢查...
@Before:目標方法為:com.abc.service.AdviceManager.manyAdvices
@Before:引數為:[改變後的引數1, bb]
@Before:被織入的目標物件為:com.abc.service.AdviceManager@1dfc617e
方法:manyAdvices
@Around:執行目標方法之後...
@Around:被織入的目標物件為:com.abc.service.AdviceManager@1dfc617e
@After:模擬釋放資源...
@After:目標方法為:com.abc.service.AdviceManager.manyAdvices
@After:引數為:[改變後的引數1, bb]
@After:被織入的目標物件為:com.abc.service.AdviceManager@1dfc617e
@AfterReturning:模擬日誌記錄功能...
@AfterReturning:目標方法為:com.abc.service.AdviceManager.manyAdvices
@AfterReturning:引數為:[改變後的引數1, bb]
@AfterReturning:返回值為:原返回值:改變後的引數1 、 bb,這是返回結果的字尾
@AfterReturning:被織入的目標物件為:com.abc.service.AdviceManager@1dfc617e
Test方法中呼叫切點方法的返回值:原返回值:改變後的引數1 、bb,這是返回結果的字尾

    從結果中可以看出:在任何一個織入的增強處理中,都可以獲取目標方法的資訊。另外,Spring AOP採用和AspectJ一樣的有限順序來織入增強處理:在“進入”連線點時,最高優先順序的增強處理將先被織入(所以給定的兩個Before增強處理中,優先順序高的那個會先執行);在“退出”連線點時,最高優先順序的增強處理會最後被織入(所以給定的兩個After增強處理中,優先順序高的那個會後執行)。當不同的切面中的多個增強處理需要在同一個連線點被織入時,Spring AOP將以隨機的順序來織入這些增強處理。如果應用需要指定不同切面類裡的增強處理的優先順序,Spring提供瞭如下兩種解決方案:

  • 讓切面類實現org.springframework.core.Ordered介面:實現該介面只需要實現一個int getOrder()方法,該方法返回值越小,優先順序越高

  • 直接使用@Order註解來修飾一個切面類:使用這個註解時可以配置一個int型別的value屬性,該屬性值越小,優先順序越高

    優先順序高的切面類裡的增強處理的優先順序總是比優先順序低的切面類中的增強處理的優先順序高。例如:優先順序為1的切面類Bean1包含了@Before,優先順序為2的切面類Bean2包含了@Around,雖然@Around優先順序高於@Before,但由於Bean1的優先順序高於Bean2的優先順序,因此Bean1中的@Before先被織入。

    同一個切面類裡的兩個相同型別的增強處理在同一個連線點被織入時,Spring AOP將以隨機的順序來織入這兩個增強處理,沒有辦法指定它們的織入順序。如果確實需要保證它們以固有的順序被織入,則可以考慮將多個增強處理壓縮為一個增強處理;或者將不同增強處理重構到不同切面中,通過在切面級別上定義順序。

    如果只要訪問目標方法的引數,Spring還提供了一種更加簡潔的方法:我們可以在程式中使用args來繫結目標方法的引數。如果在一個args表示式中指定了一個或多個引數,該切入點將只匹配具有對應形參的方法,且目標方法的引數值將被傳入增強處理方法。下面輔以例子說明:

package com.abc.advice;

import java.util.Date;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public classAccessArgAdviceTest{
    @AfterReturning(
            pointcut="execution(* com.abc.service.*.access*(..)) && args(time, name)",
            returning="returnValue")
    public void access(Date time, Object returnValue, String name) {
        System.out.println("目標方法中的引數String = " + name);
        System.out.println("目標方法中的引數Date = " + time);
        System.out.println("目標方法的返回結果returnValue = " + returnValue);
    }
}

    上面的程式中,定義pointcut時,表示式中增加了args(time, name)部分,意味著可以在增強處理方法(access方法)中定義time和name兩個屬性——這兩個形參的型別可以隨意指定,但一旦指定了這兩個引數的型別,則這兩個形參型別將用於限制該切入點只匹配第一個引數型別為Date,第二個引數型別為name的方法(方法引數個數和型別若有不同均不匹配)。

    注意,在定義returning的時候,這個值(即上面的returning="returnValue"中的returnValue)作為增強處理方法的形參時,位置可以隨意,即:如果上面access方法的簽名可以為

public void access(Date time, Object returnValue, String name)

    也可以為

public void access(Object returnValue, Date time, String name)

    還可以為

public void access(Date time, String name, Object returnValue)

    只需要滿足另外的引數名的順序和pointcut中args(param1, param2)的順序相同即可。我們在AdviceManager中定義一個方法,該方法的第一個引數為Date型別,第二個引數為String型別,該方法的執行將觸發上面的access方法,如下:

//將被AccessArgAdviceTest的access方法匹配
public String accessAdvice(Date d, String n) {
    System.out.println("方法:accessAdvice");
    return "aa";
}

    在AOPTest中增加呼叫這個accessAdvice方法並執行,下面是輸出結果:

    從執行結果可以看出,使用args表示式有如下兩個作用:

  • 提供了一種簡單的方式來訪問目標方法的引數

  • 可用於對切入點表示式作額外的限制

    除此之外,使用args表示式時,還可以使用如下形式:args(param1, param2, ..),注意args引數中後面的兩個點,它表示可以匹配更多引數。在例子args(param1, param2, ..)中,表示目標方法只需匹配前面param1和param2的型別即可。

    《Spring中的AOP系列三、四、五》的程式碼在這裡:點選下載,歡迎留言提意見。

    【未完,待續】