1. 程式人生 > >Spring之AOP深入淺出

Spring之AOP深入淺出

目錄

基本概念

其他指示符

bean:

this 

邏輯運算子

通知函式

Demo

基本概念

* 切面類;把切面應用到目標函式的過程叫做織入;
* joinPoint(連線點):指哪些目標函式可以被攔截;
* pointcut(切入點):指對joinPoint中的那些目標函式進行切入;
* advice(通知):在某個特點的pointcut(切入點)上需要執行的動作,如日誌記錄、許可權驗證等具體要應用帶切入點的程式碼
* aspect(切面):由切點和通知相結合而成,定義通知應用到那些切入點上;
* weaving(織入):把切面的程式碼織入(應用)到目標函式的過程;
* 織入的方式:動態織入和靜態織入;
* 動態織入:執行時動態講要增強對的程式碼織入目標類中,這樣往往是用過動態代理技術完成的,如JDK動態代理和CGLIB代理;Spring AOP是動態代理;
* 靜態織入:AspectJ採用的是靜態織入的方式,編譯期間織入;先將aspect類編譯成class位元組碼後,在java目標類編譯時織入。
  • 萬用字元 (..  +  *)

  ..   匹配方法定義中的任意數量的引數,此外還匹配類定義中的任意數量包;

//任意返回值,任意名稱,任意引數的公共方法
execution(public * *(..)) 

//匹配com.cn.dao包及其子包中所有類中的所有方法
within(com.cn.dao..*)

  +  匹配給定類的任意子類

//匹配實現了DaoUser介面的所有子類的方法
within(com.cn.dao.DaoUser+)

 *   匹配任意數量的字元

//匹配com.cn.service包及其子包中所有類的所有方法
within(com.cn.service..*)
//匹配以set開頭,引數為int型別,任意返回值的方法
execution(* set*(int))
  • 型別簽名表示式

     為了方便型別(如介面、類名、包名)過濾方法,Spring AOP 提供了within關鍵字。其語法格式如下:

within(<type name>)

     type name 則使用包名或者類名替換即可,例如:

//匹配com.zejian.dao包及其子包中所有類中的所有方法
@Pointcut("within(com.cn.dao..*)")

//匹配UserDaoImpl類中所有方法
@Pointcut("within(com.cn.dao.UserDaoImpl)")

//匹配UserDaoImpl類及其子類中所有方法
@Pointcut("within(com.cn.dao.UserDaoImpl+)")

//匹配所有實現UserDao介面的類的所有方法
@Pointcut("within(com.cn.dao.UserDao+)")
  • 方法簽名表示式

     如果想根據方法簽名進行過濾,關鍵字execution可以幫到我們,語法表示式如下:

//scope :方法作用域,如public,private,protect
//returnt-type:方法返回值型別
//fully-qualified-class-name:方法所在類的完全限定名稱
//parameters 方法引數
execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))

對於給定的作用域、返回值型別、完全限定類名以及引數匹配的方法將會應用切點函式指定的通知,例如:

//匹配UserDaoImpl類中的所有方法
@Pointcut("execution(* com.cn.dao.UserDaoImpl.*(..))")

//匹配UserDaoImpl類中的所有公共的方法
@Pointcut("execution(public * com.cn.dao.UserDaoImpl.*(..))")

//匹配UserDaoImpl類中的所有公共方法並且返回值為int型別
@Pointcut("execution(public int com.cn.dao.UserDaoImpl.*(..))")

//匹配UserDaoImpl類中第一個引數為int型別的所有公共的方法
@Pointcut("execution(public * com.cn.dao.UserDaoImpl.*(int , ..))")

  • 其他指示符

  • bean

          Spring AOP擴充套件的,AspectJ沒有對於指示符,用於匹配特定名稱的Bean物件的執行方法;

//匹配名稱中帶有後綴Service的Bean。
@Pointcut("bean(*Service)")
private void myPointcut1(){}
  • this 

         用於匹配當前AOP代理物件型別的執行方法;請注意是AOP代理物件的型別匹配,這樣就可能包括引入介面也型別匹配;

//匹配了任意實現了UserDao介面的代理物件的方法進行過濾
@Pointcut("this(com.cn.spring.springAop.dao.UserDao)")
private void myPointcut2(){}
  • target 

        用於匹配當前目標物件型別的執行方法;

//匹配了任意實現了UserDao介面的目標物件的方法進行過濾
@Pointcut("target(com.cn.spring.springAop.dao.UserDao)")
private void myPointcut3(){}
  • @within

   用於匹配所以持有指定註解型別內的方法;請注意與within是有區別的, within是用於匹配指定型別內的方法執行;

//匹配使用了MarkerAnnotation註解的類(注意是類)
@Pointcut("@within(com.cn.spring.annotation.MarkerAnnotation)")
private void myPointcut4(){}
  • @annotation

   根據所應用的註解進行方法過濾;

//匹配使用了MarkerAnnotation註解的方法(注意是方法)
@Pointcut("@annotation(com.cn.spring.annotation.MarkerAnnotation)")
private void myPointcut5(){}
  • 邏輯運算子

        這裡最後說明一點,切點指示符可以使用運算子語法進行表示式的混編,如and、or、not(或者&&、||、!),如下一個簡單例子:

//匹配了任意實現了UserDao介面的目標物件的方法並且該介面不在com.cn.dao包及其子包下
@Pointcut("target(com.cn.spring.springAop.dao.UserDao) !within(com.cn.dao..*)")
private void myPointcut6(){}
//匹配了任意實現了UserDao介面的目標物件的方法並且該方法名稱為addUser
@Pointcut("target(com.cn.spring.springAop.dao.UserDao)&&execution(* com.cn.spring.springAop.dao.UserDao.addUser(..))")
private void myPointcut7(){}
  • 通知函式

  • 前置通知@Before

前置通知通過@Before註解進行標註,並可直接傳入切點表示式的值,該通知在目標函式執行前執行,注意JoinPoint,是Spring提供的靜態變數,通過joinPoint 引數,可以獲取目標物件的資訊,如類名稱,方法引數,方法名稱等,,該引數是可選的。

 @Before("doAspectWithin()")
    public void beforeAspect(JoinPoint joinPoint) {
        LOGGER.info("Begin to run...");
        System.out.println(joinPoint.getTarget().getClass());
    }
  • 後置通知@AfterReturning 

通過@AfterReturning註解進行標註,該函式在目標函式執行完成後執行,並可以獲取到目標函式最終的返回值returnVal,當目標函式沒有返回值時,returnVal將返回null,必須通過returning = “returnVal”註明引數的名稱而且必須與通知函式的引數名稱相同。請注意,在任何通知中這些引數都是可選的,需要使用時直接填寫即可,不需要使用時,可以完成不用宣告出來。

/**
* 後置通知,不需要引數時可以不提供
*/
@AfterReturning(value="execution(* com.cn.spring.springAop.dao.UserDao.*User(..))")
public void AfterReturning(){
   System.out.println("我是後置通知...");
}
/**
* 後置通知
* returnVal,切點方法執行後的返回值
*/
@AfterReturning(value="execution(* com.cn.spring.springAop.dao.UserDao.*User(..))",returning = "returnVal")
public void AfterReturning(JoinPoint joinPoint,Object returnVal){
   System.out.println("我是後置通知...returnVal+"+returnVal);
}
  • 異常通知 @AfterThrowing

該通知只有在異常時才會被觸發,並由throwing來宣告一個接收異常資訊的變數,同樣異常通知也用於Joinpoint引數,需要時加上即可.

   @AfterThrowing(value = "execution(* com.cn.smart.controller.HelloWorldController.*(..))", throwing = "e")
    public void afterThrowable(Throwable e) {
        LOGGER.error("Run failed, for " + e.getMessage());
    }
  • 最終通知 @After

該通知有點類似於finally程式碼塊,只要應用了無論什麼情況下都會執行。

/**
 * 無論什麼情況下都會執行的方法
 * joinPoint 引數
 */
@After("execution(* com.cn.spring.springAop.dao.UserDao.*User(..))")
public void after(JoinPoint joinPoint) {
    System.out.println("最終通知....");
}
  • 環繞通知@Around 

環繞通知既可以在目標方法前執行也可在目標方法之後執行,更重要的是環繞通知可以控制目標方法是否指向執行,但即使如此,我們應該儘量以最簡單的方式滿足需求,在僅需在目標方法前執行時,應該採用前置通知而非環繞通知。案例程式碼如下第一個引數必須是ProceedingJoinPoint,通過該物件的proceed()方法來執行目標函式,proceed()的返回值就是環繞通知的返回值。同樣的,ProceedingJoinPoint物件也是可以獲取目標物件的資訊,如類名稱,方法引數,方法名稱等等。 --------------------- 本文來自 zejian_ 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/javazejian/article/details/56267036?utm_source=copy

@Around("execution(* com.cn.spring.springAop.dao.UserDao.*User(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("我是環繞通知前....");
    //執行目標函式
    Object obj= (Object) joinPoint.proceed();
    System.out.println("我是環繞通知後....");
    return obj;
}
  • 通知傳遞引數

待續。。。

  • Aspect優先順序

待續。。。

  • Demo

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface LogAspect {

    //執行的操作型別,例如:Add
    public String operationType() default "";

    //執行的具體操作名稱:新增使用者
    public String operationName() default "";
}

切面類: 

@Aspect   //切面類註解
@Component
public class SystemLogAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(SystemLogAspect.class);

    //使用了LogAspect註解的方法會被切
    @Pointcut("@annotation(com.cn.smart.annotation.LogAspect)")
    public void doAspectAnnotation() {
    }

    //所有的該包下面的所有的方法(目標方法)都會被加入切面
    @Pointcut("execution(* com.cn.smart.controller.*.*(..))")
    public void doAcpectExecution() {
    }

    //
    @Pointcut("within(com.cn.smart.controller.*)")
    public void doAspectWithin() {
    }

    //環繞通知,即在執行proceed()方法前後執行橫切關注點;帶名的;
    @Around("doAspectAnnotation()")
    public Object aroundAspect(final ProceedingJoinPoint joinPoint) throws Throwable {
        Long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        LOGGER.info("Run time: {}", System.currentTimeMillis() - startTime);
        return result;
    }

    //後置通知
    @After("doAcpectExecution()")
    public void afterAspect() {
        LOGGER.info("Run finished!!!");
    }

    @Before("doAspectWithin()")
    public void beforeAspect(JoinPoint joinPoint) {
        LOGGER.info("Begin to run...");
        System.out.println(joinPoint.getTarget().getClass());
    }


    @AfterThrowing(value = "execution(* com.cn.smart.controller.HelloWorldController.*(..))", throwing = "e")
    public void afterThrowable(Throwable e) {
        LOGGER.error("Run failed, for " + e.getMessage());
    }

    @AfterReturning(value = "execution(* com.cn.smart.controller.HelloWorldController.*(..))", returning = "result")
    public void afterReturning(Object result) {
        LOGGER.info("Run return:{}", result);
    }

}

controller層的程式碼:

@RestController
@RequestMapping("/aop")
public class HelloWorldController {

    @LogAspect(operationName = "test", operationType = "get")
    @GetMapping(value = "/test/annotation")
    public String testSpringAop() {
        System.out.println("Begin to test Spring-aop annotation...");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("data", "Hello Spring-Aop annotation demo");
        return jsonObject.toJSONString();
    }

}