你不是說你會Aop嗎?

Java旅途發表於2020-08-04

一大早,小王就急匆匆的跑過來找我,說:周哥,那個記錄日誌的功能我想請教一下。

因為公司某個專案要跟別的平臺做對接,我們這邊需要給他們提供一套介面。昨天,我就將記錄介面日誌的工作安排給了小王。

下面是我跟小王的主要對話。

我:說說怎麼了?

小王:我將記錄介面日誌的功能放到了每個controller中,現在感覺有點繁瑣,我這樣做是不是不太合適?

我:為什麼要去每個介面裡記錄日誌?

小王:最開始我是用的攔截器,但是這樣一個請求就記錄了兩條記錄。

我:為什麼是兩條?

小王:在preHandle中記錄一條請求資料,在postHandle中記錄一條響應資料。

我:。。。你不是說你會Aop嗎?

小王:Aop也是一樣,在前置通知記錄一條請求資料,後置通知記錄一條響應資料。

小王:這個資料和以前記錄操作日誌的不太一樣,以前只需要在前置通知記錄一條操作日誌就可以了,但是現在有響應,所以只能在controller中記錄日誌了。

我:那你知不知道有個環繞通知?你說一下Aop就幾種通知型別。

小王:總共有五種,分別是:

  • 前置通知:在我們執行目標方法之前執行(@Before
  • 後置通知:在我們目標方法執行結束之後,不管有沒有異常(@After
  • 返回通知:在我們的目標方法正常返回值後執行(@AfterReturning
  • 異常通知:在我們的目標方法出現異常後執行(@AfterThrowing
  • 環繞通知:目標方法的呼叫由環繞通知決定,即你可以決定是否呼叫目標方法,joinPoint.procced()就是執行目標方法的程式碼 。環繞通知可以控制返回物件(@Around)

接下來,我們一起來演示一下如何使用環繞通知來解決小王的問題。

第一步:提供介面用來接收引數和響應介面

@RestController
public class TestController {
    @GetMapping("/getName")
    public String getName(HttpServletRequest request) throw Exception {

        String result = "Java旅途";
        String age = request.getParameter("age");
        if("18".equals(age)){
            result = "無法識別";
        }
        return result;
    }
}

第二步:定義切點

execution()是比較常用的定義切點的表示式,execution()語法如下:

execution(修飾符  返回值  包.類.方法名(引數) throws異常)

其中:

修飾符和throws異常可以省略不寫

根據這些解釋,我們可以將第一步中的介面用execution()表示式來描述一下:

execution(String binzh.website.controller.TestController.GetName(HttpServletRequest))
  • *:匹配所有項

  • ..:匹配任意個方法引數

  • ..出現在類名中時,後面必須跟*,表示包、子孫包下的所有類;

現在我們優化一下上面的表示式,定義切面為controller包及controller下面所有包的所有方法

execution(* binzh.website.controller..*.*(..))

第三步:環繞通知記錄日誌

@Around("execution(* binzh.website.controller..*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) {
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();
    String age = request.getParameter("age");
    Object proceed = "";
    try {
        proceed = joinPoint.proceed();
    } catch (Throwable e) {
        e.printStackTrace();
    }
    System.out.println("age==="+age);
    System.out.println("proceed ===="+proceed);
    return proceed;
}

執行結果如下:

age===19
proceed ====Java旅途

我們之所以可以用環繞通知來處理小王的問題。其中一個重要的原因就是,我們提供的所有介面都是經過統一加密的,最後請求的引數都是一個固定的名字。還需要注意的一點就是,環繞通知的返回值型別必須大於等於方法的返回值,即:加入你方法返回String型別,環繞通知不能寫成void型別

小王看到這裡後,恍然大悟,準備趕緊回去試一下。我急忙拉住他。

我:如果介面出現異常了怎麼辦?

小王:那我在異常通知裡處理就可以了。

我:你再想一下?

小王:好像不行,異常通知裡獲取不到請求引數。

我:在環繞通知中捕獲處理可以嗎?

這時候,看見小王眼睛發光,驚訝的說了一句:環繞通知太牛批了,竟然可以完成前置通知、後置通知和異常通知的工作!

這篇文章戲有點多,別見怪。實戰是提升技術最有效的途徑!

相關文章