1. 程式人生 > 程式設計 >Spring Aop如何給Advice傳遞引數

Spring Aop如何給Advice傳遞引數

給Advice傳遞引數

Advice除了可以接收JoinPoint(非Around Advice)或ProceedingJoinPoint(Around Advice)引數外,還可以直接接收與切入點方法執行有關的物件,比如切入點方法引數、切入點目標物件(target)、切入點代理物件(this)等。

1獲取切入點方法引數

假設我們現在有一個id為userService的bean中定義了一個findById(int id)方法,我們希望定義一個Advice來攔截這個方法,並且把findById()的引數作為Advice處理方法的引數,即每次呼叫findById()傳遞的引數都將傳遞到Advice處理方法,那麼我們可以如下這樣定義。

@Before(value="bean(userService) && execution(* findById(java.lang.Integer)) && args(id)",argNames="id")
public void beforeWithParam(JoinPoint joinPoint,Integer id) {
  System.out.println(this.getClass().getName()+" ID is : " + id);
}

上面這種定義是非常精確的定義,我們通過表示式“bean(userService) && execution(* findById(java.lang.Integer))”就已經明確的指定了我們需要攔截的是id或name為userService的findById(Integer)方法,後面又加了一個args(id)是幹什麼用的呢?它的作用跟findById(Integer)是類似的,它表示我們的切入點方法必須只接收一個引數,而且這個引數的型別是和當前定義的Advice處理方法的引數id是相同型別的,在上面的示例中其實就是要求是Integer型別的;另外它還有一個非常重要的作用,通過這種指定後對應的Advice處理方法在執行時將接收到與之對應的切入點方法引數的值。在上面的示例中筆者特意給Advice處理方法加了一個JoinPoint引數是為了說明JoinPoint、ProceedingJoinPoint引數是可以直接定義在Advice方法的第一個引數,並且是可以與其它接收的引數共存的。其實如果我們不只是需要攔截findById(Integer)方法,而是需要攔截id為userService的bean中所有接收一個int/Integer引數的方法,那麼我們可以把上面的配置簡化為如下這樣。

@Before(value="bean(userService) && args(id)",argNames="id")
public void beforeWithParam2(int id) {
   System.out.println(this.getClass().getName()+" ID is : " + id);
}

如果我們需要攔截的方法可能是有多個引數的,但我們只關注第一個引數,那我們可以把表示式調整為如下這樣,只關注第一個引數為int/Integer型別的,並且在Advice方法中接收這個方法引數進行相應的處理。

@Before(value="bean(userService) && args(id,..)",argNames="id")
public void beforeWithParam2(int id) {
   System.out.println(this.getClass().getName()+" ID is : " + id);
}

2 argNames引數

我們可以看到在上述例子中我們都指定了@Before的argNames屬性的值為id,那麼這個argNames屬性有什麼作用呢?argNames屬性是用於指定在表示式中應用的引數名與Advice方法引數是如何對應的,argNames中指定的引數名必須與表示式中的一致,可以與Advice方法引數名不一致;當表示式中使用了多個引數時,argNames中需要指定多個引數,多個引數之間以英文逗號分隔,這些引數的順序必須與對應的Advice方法定義的引數順序是一致的。

比如下面這個示例中,我們在Pointcut表示式中使用了name和sex兩個引數,我們的Advice處理方法接收兩個引數,分別是sex1和name1,我們希望Pointcut表示式中的name引數是對應的Advice處理方法的第二個引數,即name1,希望Pointcut表示式中的sex引數是對應的Advice處理方法的第一個引數,即sex1,那麼我們在指定@Before註解的argNames引數時必須定義name和sex引數與Advice處理方法引數的關係,且順序要求與對應的處理方法的引數順序一致,即哪個引數是需要與Advice處理方法的第一個引數匹配則把哪個引數放第一位,與第二個引數匹配的則放第二位,在我們的這個示例中就應該是sex放第一位,name放第二位。

@Before(value="bean(userService) && args(name,sex)",argNames="sex,name")
public void beforeWithParam3(int sex1,String name1) {
  System.out.println("sex is : " + sex1);
  System.out.println("name is : " + name1);
}

@Before註解的argNames引數不是必須的,它只有在我們編譯的位元組碼中不含DEBUG資訊或Pointcut表示式中使用的引數名與Advice處理方法的引數名不一致時才需要。所以在編譯的位元組碼中包含DEBUG資訊且Advice引數名與Pointcut表示式中使用的引數名一致時,我們完全可以把argNames引數省略。如果表示式裡面使用了多個引數,那麼這些引數在表示式中的順序可以與Advice方法對應引數的順序不一致,例如下面這個樣子。

@Before(value="bean(userService) && args(id)")
public void beforeWithParam2(int id) {
  System.out.println(this.getClass().getName()+" ID is : " + id);
}

3 獲取this物件

this物件就是Spring生成的bean的那個代理物件。如下示例就是Advice方法接收this物件,我們給Advice方法指定一個需要攔截的this物件型別的引數,然後在表示式中使用this型別的表示式定義,表示式中定義的對應型別指定為Advice方法引數。

@Before("this(userService)")
public void beforeWithParam4(IUserService userService) {
    //this物件應該是一個代理物件
    System.out.println(this.getClass().getName()+"==============傳遞this對
    象: " + userService.getClass());
}

4 混合使用

我們的Advice方法可以同時接收多個目標方法引數,與此同時它也可以接收this等物件,即它們是可以混合使用的。下面這個示例中我們就同時接收了this物件和目標方法int/Interger型別的引數。

@Before("this(userService) && args(id)")
public void beforeWithParam5(IUserService userService,int id) {
    System.out.println(this.getClass().getName()+"===========" + id +
    "==============" + userService.getClass());
}

5 獲取target物件

獲取target物件也比較簡單,只需要把表示式改為target型別的表示式即可。

@Before("target(userService)")
public void beforeWithParam6(IUserService userService) {
    System.out.println(this.getClass().getName()+"==============傳遞
    target物件: " + userService.getClass());
}

6 獲取註解物件

當我們的Pointcut表示式型別是通過註解匹配時,我們也可以在Advice處理方法中獲取匹配的註解物件,如下面這個示例,其它如使用@target等是類似的。

@Before("@annotation(annotation)")
public void beforeWithParam7(MyAnnotation annotation) {
    System.out.println(this.getClass().getName()+"==============傳遞標
    注在方法上的annotation: " + annotation.annotationType().getName());
}

7 泛型引數

有的時候我們的Advice方法需要接收的切入點方法引數定義的不是具體的型別,而是一個泛型,這種情況下怎麼辦呢?可能你會想那我就把對應的Advice方法引數定義為Object型別就好了,反正所有的型別都可以轉換為Object型別。對的,這樣是沒有錯的,但是說如果你只想攔截某種具體型別的引數呼叫時就可以不用把Advice方法引數型別定義為Object了,這樣還得在方法體裡面進行判斷,我們可以直接把Advice方法引數型別定義為我們想攔截的方法引數型別。比如我們有下面這樣一個使用了泛型的方法定義,我們希望只有在呼叫testParam方法時傳遞的引數型別是Integer型別時才進行攔截。

public <T> void testParam(T param);

那這個時候我們就可以把我們的Advice的表示式定義為如下這樣,前者精確定義接收方法名為testParam,返回型別為void,後者精確定義方法引數為一個Integer型別的引數,其實前者也可以定義為“execution(void testParam(Integer))”。看到這你可能會想,為什麼不直接把表示式定義為“execution(void testParam(param))”呢?因為execution是不支援Advice方法引數繫結的,基本上支援Advice引數繫結的就只有this、target、args以及對應的註解形式加@annotation。

@Before("execution(void testParam(..)) && args(param)")
public void beforeWithParam8(Integer param) {
    System.out.println("pointcut expression[args(param)]--------------param:" +
    param);
}

以上就是常用的傳遞引數給Advice處理方法的方式,有一些示例可能沒有講到,比如@target這種,這些其實都是類似的。包括上面我們都是以@Before這種Advice來講的,其實其它的Advice在接收引數的時候也是類似的。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。