Spring學習筆記(八、Spring對AspectJ的支持)

上一篇:Spring學習筆記(七、Spring AOP API)

一、AspectJ介紹與Pointcut注解應用

1. AspectJ

  • @AspectJ的風格類似純java注解的普通java類。
  • Spring可以使用AspectJ來做切入點分析。
  • AOP的運行時仍舊是純的Spring AOP,對AspectJ的編譯器或者織入無依賴性。

2. Spring中配置AspectJ

  • 對@AspectJ支持可以使用XML或Java風格配置。
  • 確保AspectJ的aspectjweaver.jar庫包含在應用程序(版本1.6.8或更高版本)的classpath中。


    Paste_Image.png

3. @Aspect注解

  • AspectJ切面使用@Aspect注解配置,擁有@Aspect注解的任何bean,將被Spring自動識別并應用。
  • 用@Aspect注解的類可以有方法和字段,他們也可能包括切入點(pointcut)、通知(advice)、和引入(introduction)聲明。
  • @Aspect注解是不能通過類路徑自動檢測發現的,所以需要配合使用@Component注釋或者在xml中配置bean。
  • 一個類中的@Aspect注解標識它為一個切面,并且將自己從自動代理中排除。

4. pointcut

  • 一個切入點通過一個普通的方法定義來提供,并且切入點表達式使用@Pointcut注解,方法返回類型必須為void。
指示符 說明
execution 匹配方法執行的連接點
within 限定匹配特定類型的連接點
this 匹配特定連接點的bean引用,是指定類型的實例的限制
target 限定匹配特定連接點的目標對象是指定類型的實例
args 限定匹配特定連接點的參數是給定類型的實例
@target 限定匹配特定連接點的類執行對象的具有給定類型的注解
@args 限定匹配特定連接點實際傳入的參數的類型具有給定類型的注解
@within 限定匹配到內具有給定的注釋類型的連接點
@annotation 限定匹配特定連接點的主體具有給定的注解

創建一個切面類:AmberAspect

package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
 * Created by amber on 2017/6/20.
 */
@Component
@Aspect
public class AmberAspect {
    @Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
    public void pointcut(){
    }
    @Pointcut("within(com.amber.aop.biz.*)")
    public void bizPointcut(){}
}

5. 組合pointcut

  • 切入點表達式可以通過&&、||和!進行組合,也可以通過名字引用切入點表達式。
  • 通過組合,可以建立更加復雜的切入點表達式。


    Paste_Image.png

6. 定義良好的pointcuts

  • AspectJ是編譯器的AOP。
  • 檢查代碼并匹配連接點與切入點的代價是昂貴的。
  • 一個好的切入點應該包括以下幾點:
    • 選擇特定類型的連接點。如:execution、get、set、call、handler
    • 確定連接點范圍,如:within、withincode
    • 匹配上下文信息,如:this、target、@annotation

二、Advice定義及實例

1. Before advice
更新AmberAspect 切面類:

package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
 * Created by amber on 2017/6/20.
 */
@Component
@Aspect
public class AmberAspect {
    @Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
    public void pointcut(){
    }

    @Pointcut("within(com.amber.aop.biz.*)")
    public void bizPointcut(){}

    @Before("execution(* com.amber.aop.biz.*Biz.*(..))")
    public void before(){
        System.out.println("前置通知!");
    }
}

創建業務類AspectBiz :

package com.amber.aop.biz;
import org.springframework.stereotype.Service;
import test12.StringStore;
/**
 * Created by amber on 2017/6/18.
 * 業務類
 */
@Service
public class AspectBiz {
    public String save(String args){
        System.out.println("執行AspectBiz的save方法,參數:"+args);
        return "Save Success";
    }
}

applicationContext:

    <context:component-scan base-package="com.amber.aop"></context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

測試類:

  @Test
    public void test17() {
        AspectBiz aspectBiz=super.getBean("aspectBiz");
        aspectBiz.save("淡雅如菊,溫潤如玉");
    }

結果:


Paste_Image.png

修改AmberAspect 切面類,將前置通知的表達式替換成同樣表達式的pointcut()方法:

 package com.amber.aop.aspectj;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * Created by amber on 2017/6/20.
 */
@Component
@Aspect
public class AmberAspect {

    @Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
    public void pointcut(){

    }
    @Pointcut("within(com.amber.aop.biz.*)")
    public void bizPointcut(){}

    @Before("pointcut()")
    public void before(){
        System.out.println("前置通知!");
    }
}

結果:


Paste_Image.png

2. After Returning Advice

  • 有時候需要在通知體內得到返回的實際值,可以使用@AfterReturning綁定返回值的形式。
    更新AmberAspect 切面類,增加After Returning通知:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
 * Created by amber on 2017/6/20.
 */
@Component
@Aspect
public class AmberAspect {
    @Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
    public void pointcut(){

    }
    @Pointcut("within(com.amber.aop.biz.*)")
    public void bizPointcut(){}

    @Before("pointcut()")
    public void before(){
        System.out.println("前置通知!");
    }

    @AfterReturning(pointcut = "bizPointcut()",returning = "returnValue")
    public void afterReturning(Object returnValue){
        System.out.println("返回后通知,返回值為:"+returnValue);
    }
}

結果:


Paste_Image.png

3. After throwing advice

  • 有時候需要在通知體內得到返回的實際值,可以使用@AfterThrowing綁定返回值的形式。
    更新AmberAspect 切面類,增加After Throwing通知:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
 * Created by amber on 2017/6/20.
 */
@Component
@Aspect
public class AmberAspect {

    @Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
    public void pointcut(){

    }
    @Pointcut("within(com.amber.aop.biz.*)")
    public void bizPointcut(){}

    @Before("pointcut()")
    public void before(){
        System.out.println("前置通知!");
    }

    @AfterReturning(pointcut = "bizPointcut()",returning = "returnValue")
    public void afterReturning(Object returnValue){
        System.out.println("返回后通知,返回值為:"+returnValue);
    }

    @AfterThrowing(pointcut = "bizPointcut()",throwing = "e")
    public void afterThrowing(Exception e){
        System.out.println("異常后通知,異常為:"+e);
    }
}

修改類:

package com.amber.aop.biz;
import org.springframework.stereotype.Service;
import test12.StringStore;
/**
 * Created by amber on 2017/6/18.
 * 業務類
 */
@Service
public class AspectBiz {
    public String save(String args){
        System.out.println("執行AspectBiz的save方法,參數:"+args);
        throw new RuntimeException("Save failed");
        //return "Save Success";
    }
}

結果:


Paste_Image.png

3. After(finally) advice

  • 最終通知必須準備處理正常和異常兩種返回情況,它通常用于釋放資源。
    更新AmberAspect 切面類,增加After通知:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
 * Created by amber on 2017/6/20.
 */
@Component
@Aspect
public class AmberAspect {
    @Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
    public void pointcut(){

    }
    @Pointcut("within(com.amber.aop.biz.*)")
    public void bizPointcut(){}

    @Before("pointcut()")
    public void before(){
        System.out.println("前置通知!");
    }

    @AfterReturning(pointcut = "bizPointcut()",returning = "returnValue")
    public void afterReturning(Object returnValue){
        System.out.println("返回后通知,返回值為:"+returnValue);
    }

    @AfterThrowing(pointcut = "bizPointcut()",throwing = "e")
    public void afterThrowing(Exception e){
        System.out.println("異常后通知,異常為:"+e);
    }

    @After("bizPointcut()")
    public void after(){
        System.out.println("后置通知!");
    }
}

結果:


Paste_Image.png

將業務類AspectBiz異常注釋:

package com.amber.aop.biz;
import org.springframework.stereotype.Service;
import test12.StringStore;
/**
 * Created by amber on 2017/6/18.
 * 業務類
 */
@Service
public class AspectBiz {
    public String save(String args){
        System.out.println("執行AspectBiz的save方法,參數:"+args);
//        throw new RuntimeException("Save failed");
        return "Save Success";
    }
}

結果:


Paste_Image.png

4. Around advice

  • 環繞通知使用@Around注解來聲明,通知方法的第一個參數必須是ProceedingJoinPoint類型。
  • 在通知內部會調用ProceedingJoinPoint的proceed()方法會導致執行真正的方法,傳入一個Object[]對象,數組中的值將被作為參數傳遞給方法。
    更新AmberAspect ,增加Around通知:
package com.amber.aop.aspectj;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
 * Created by amber on 2017/6/20.
 */
@Component
@Aspect
public class AmberAspect {
    @Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
    public void pointcut(){

    }
    @Pointcut("within(com.amber.aop.biz.*)")
    public void bizPointcut(){}

    @Before("pointcut()")
    public void before(){
        System.out.println("前置通知!");
    }

    @AfterReturning(pointcut = "bizPointcut()",returning = "returnValue")
    public void afterReturning(Object returnValue){
        System.out.println("返回后通知,返回值為:"+returnValue);
    }

    @AfterThrowing(pointcut = "bizPointcut()",throwing = "e")
    public void afterThrowing(Exception e){
        System.out.println("異常后通知,異常為:"+e);
    }

    @After("bizPointcut()")
    public void after(){
        System.out.println("后置通知!");
    }

    @Around("bizPointcut()")
    public Object around(ProceedingJoinPoint pjp)throws Throwable{
        System.out.println("環繞通知,pjp.proceed();執行前!");
        Object obj=pjp.proceed();
        System.out.println("環繞通知,pjp.proceed();執行后!  返回值為:"+obj);
        return obj;
    }
}

結果:


Paste_Image.png

三、給Advice傳遞參數

1. 給Advice傳遞參數

Paste_Image.png

  1. 創建一個AmberAspectTwo切面類,增加帶參的前置通知,傳入普通參數,將之前的AmberAspect類的@Aspect注釋掉:
package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
 * Created by amber on 2017/6/20.
 */
@Component
@Aspect
public class AmberAspectTwo{

    @Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
    public void pointcut() {
    }

    @Pointcut("within(com.amber.aop.biz.*)")
    public void bizPointcut() {
    }

    @Pointcut("pointcut()&&args(arg)")
    public void before(String arg) {
        System.out.println("前置通知,獲取參數為:" + arg);
    }
}

結果:


Paste_Image.png
  1. 創建一個自定義注解:
package com.amber.aop.aspectj;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * Created by amber on 2017/6/20.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AmberMethod {
    String value();
}

修改業務類方法,為其添加一個自定義注解:

package com.amber.aop.biz;
import com.amber.aop.aspectj.AmberMethod;
import org.springframework.stereotype.Service;
import test12.StringStore;
/**
 * Created by amber on 2017/6/18.
 * 業務類
 */
@Service
public class AspectBiz {
    @AmberMethod("這是我自定義的注解")
    public String save(String args){
        System.out.println("執行AspectBiz的save方法,參數:"+args);
//        throw new RuntimeException("Save failed");
        return "Save Success";
    }
}

更新AmberAspectTwo切面類,增加帶注解參數的后置通知:

package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
 * Created by amber on 2017/6/20.
 */
@Component
@Aspect
public class AmberAspectTwo {

    @Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
    public void pointcut() {
    }

    @Pointcut("within(com.amber.aop.biz.*)")
    public void bizPointcut() {
    }

    @Before("pointcut()&&args(arg)")
    public void beforeWithArgs(String arg) {
        System.out.println("前置通知,獲取參數為:" + arg);
    }

    @After("pointcut()&&@annotation(amberMethod)")
    public void afterWithAnnotation(AmberMethod amberMethod) {
        System.out.println("后置通知,獲取參數為:" + amberMethod.value());
    }
}

結果:


Paste_Image.png

修改AmberAspectTwo 切面類修改其后置通知的值引用bizPointcut()方法的組合切入點。

package com.amber.aop.aspectj;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
 * Created by amber on 2017/6/20.
 */
@Component
@Aspect
public class AmberAspectTwo {

    @Pointcut("execution(* com.amber.aop.biz.*Biz.*(..))")
    public void pointcut() {
    }

    @Pointcut("within(com.amber.aop.biz.*) && @annotation(amberMethod)")
    public void bizPointcut(AmberMethod amberMethod) {
    }

    @Before("pointcut()&&args(arg)")
    public void beforeWithArgs(String arg) {
        System.out.println("前置通知,獲取參數為:" + arg);
    }

    @After("bizPointcut(amberMethod)")
    public void afterWithAnnotation(AmberMethod amberMethod) {
        System.out.println("后置通知,獲取參數為:" + amberMethod.value());
    }
}

結果:


Paste_Image.png

2. Advice的參數及泛型

  • Spring AOP可以處理泛型類的聲明和使用方法的參數。


    Paste_Image.png

3. Advice參數名稱

  • 通知和切入點有一個額外的“argName”屬性,它可以用來指定所注解的方法的參數名。


    Paste_Image.png
  • 如果第一個參數是JoinPoint,ProceedingJoinPoint,JoinPoint.StaticPart,那么可以忽略它。


    Paste_Image.png

3. Introductions

  • 允許一個切面聲明一個通知對象實現指定接口,并且提供了一個接口實現類來代表這些對象。
  • Introduction使用@DeclareParents進行注解,這個注解用來定義匹配的類型擁有一個新的parent。


    Paste_Image.png

4. 切面實例化模型

  • 這是一個高級主題
  • “perthis” 切面通過指定@Aspect注解perthis子句實現。
  • 每個獨立的service對象執行時都會創建一個切面實例。
  • service對象的每個方法在第一次執行的時候創建切面實例,切面在service對象失效的時候同時失效。


    Paste_Image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容