你不是說你會Aop嗎?

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

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

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

我:說說怎麼了?

小王:我將記錄接口日誌的功能放到了每個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類型

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

我:如果接口出現異常了怎麼辦?

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

我:你再想一下?

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

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

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

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