1. 程式人生 > >輕鬆搞定AOP

輕鬆搞定AOP

整合Spring AOP步驟

1 引入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.實體

@Slf4j
@Aspect
@Order(3)   // 有多個日誌時,ORDER可以定義切面的執行順序(數字越大,前置越後執行,後置越前執行)
@Component
public class LogAspect {
​
    ThreadLocal<Long> startTime = new ThreadLocal<>();
​
    @Pointcut("execution(public * cn.myframe.controller..*.*(..))")
    public void log() {
    }
​
    @Before("log()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        startTime.set(System.currentTimeMillis());
        // 接收到請求,記錄請求內容
        ServletRequestAttributes attributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
​
        // 記錄下請求內容
        log.info("URL : " + request.getRequestURL().toString());
        log.info("HTTP_METHOD : " + request.getMethod());
        log.info("IP : " + request.getRemoteAddr());
        log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName()
                + "." + joinPoint.getSignature().getName());
        log.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));
    }
​
    @AfterReturning(returning = "ret", pointcut = "log()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 處理完請求,返回內容
        log.info("RESPONSE : " + ret);
        log.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get()));
    }
}

通知位置的切入內容

  • 使用@Before在切入點開始處切入內容

  • 使用@After在切入點結尾處切入內容

  • 使用@AfterReturning在切入點return內容之後切入內容(可以用來對處理返回值做一些加工處理)

  • 使用@Around在切入點前後切入內容,並自己控制何時執行切入點自身的內容

  • 使用@AfterThrowing用來處理當切入內容部分丟擲異常之後的處理邏輯

AOP切面的優先順序

由於通過AOP實現,程式得到了很好的解耦,但是也會帶來一些問題,比如:我們可能會對Web層做多個切面,校驗使用者,校驗頭資訊等等,這個時候經常會碰到切面的處理順序問題。

@Order(i)註解來標識切面的優先順序。i的值越小,優先順序越高。假設我們還有一個切面是CheckNameAspect用來校驗name必須為didi,我們為其設定@Order(10),而上文中WebLogAspect設定為@Order(5),所以WebLogAspect有更高的優先順序,這個時候執行順序是這樣的:

在@Before中優先執行@Order(5)的內容,再執行@Order(10)的內容
在@After和@AfterReturning中優先執行@Order(10)的內容,再執行@Order(5)的內容
所以我們可以這樣子總結:

在切入點前的操作,按order的值由小到大執行
在切入點後的操作,按order的值由大到小執行


定義切入點

切入點表示式的格式:execution([可見性] 返回型別 [宣告型別].方法名(引數) [異常])

其中[]的為可選,其他的還支援萬用字元的使用:

  • *:匹配所有字元

  • ..:一般用於匹配多個包,多個引數

  • +:表示類及其子類

運算子有:&&、||、!

@Pointcut("execution(public * cn.myframe.controller..*.*(..))")
上面這段註解的意思如下:
1) execution(): 表示式主體
2) 第一個public *號:表示返回型別, *號表示所有的型別。
3) 包名:表示需要攔截的包名,後面的兩個句點表示當前包和當前包的所有子包,com.king.controller包、子孫包下所有類的方法。
4) 第二個*號:表示類名,*號表示所有的類。
5) *(..):最後這個星號表示方法名,*號表示所有的方法,後面括弧裡面表示方法的引數,兩個句點表示任何引數


常用切入點表示式關鍵詞

1)execution:用於匹配子表示式。

  //匹配com.cjm.model包及其子包中所有類中的所有方法,返回型別任意,方法引數任意
  @Pointcut("execution(* com.cjm.model...(..))")
  public void before(){}


2)within:用於匹配連線點所在的Java類或者包。

 //匹配Person類中的所有方法
 @Pointcut("within(com.cjm.model.Person)")
 public void before(){}
 
 //匹配com.cjm包及其子包中所有類中的所有方法
 @Pointcut("within(com.cjm..*)")
 public void before(){}


3)@annotation :匹配連線點被它引數指定的Annotation註解的方法。也就是說,所有被指定註解標註的方法都將匹配。

@Pointcut("@annotation(com.cjm.annotation.AdviceAnnotation)")
public void before(){}


4)target:用於向通知方法中傳入目標物件的引用。

@Before("before() && target(target)
public void beforeAdvide(JoinPoint point, Object proxy){
}


5)bean:通過受管Bean的名字來限定連線點所在的Bean。該關鍵詞是Spring2.5新增的。

@Pointcut("bean(person)")
 public void before(){}


6)args:用於將引數傳入到通知方法中。

args來繫結。如果在一個args表示式中應該使用型別名字的地方 使用一個引數名字,那麼當通知執行的時候對應的引數值將會被傳遞進來。

@Before("before() && args(age,username)")
public void beforeAdvide(JoinPoint point, int age, String username){
}