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

獲取目標方法的信息java

    訪問目標方法最簡單的作法是定義加強處理方法時,將第一個參數定義爲JoinPoint類型,當該加強處理方法被調用時,該JoinPoint參數就表明了織入加強處理的鏈接點。JoinPoint裏包含了以下幾個經常使用的方法:
git

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

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

  • Object getTarget:返回被織入加強處理的目標對象框架

  • Object getThis:返回AOP框架爲目標對象生成的代理對象spa

    注意:當使用@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 class AdviceTest {
    @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類中增長如下內容:code

//將被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 class AccessArgAdviceTest {
    @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系列3、4、五》的代碼在這裏:點擊下載,歡迎留言提意見。

    

    【未完,待續】

相關文章
相關標籤/搜索