[Day 27] 遠征 Kotlin × Spring Boot 介紹 Spring AOP 機制

切面導向程式設計(Aspect-oriented programming, AOP),又譯為面向方面程式設計、剖面導向程式設計,此設計最主要目的是實現關注點分離(Separation of concerns),希望將專案的橫切關注點與業務核心主體進行分離,以提高程式碼的模組化程度,使得我們可以直接將與核心業務功能關係較不相關的功能直接添加至程式中,同時又不會造成核心功能的程式可讀性複雜化,例如 Log 紀錄檔功能。

故 AOP 機制可以讓我們將一些非功能性配置與核心業務功能進行分離,非功能性配置有例如日誌紀錄、效能統計、安全控制、事務處理、異常處理等配置,此優點又可以讓我們更專注在業務邏輯上的開發,不會有非功能性配置與業務功能耦合性問題。

AOP 有以下相關主要術語:

  • Aspect 切面:由 切入點(PointCut)通知(Advice)組成,主要就是用來設定切入點(PointCut)與切入特定動作(Advice)
  • PointCut 切點:設定要被 AOP 切入的位置,例如某個類別或函數
  • JoinPoint 連接點:為 PointCut 切入後的實際切入點,通常是一個函數
  • Advice 通知:為 Joint Point 切入點實際要執行的動作,通常會將 Advice 模擬為一個攔截器(Interceptor),並且會在連接點(Join Point)上維護多個 Advice 進行層層攔截

Advice 又可以分為五種類型:

  • @Before 前置通知 — 在呼叫方法前執行
  • @AfterReturn 正常返回通知 — 正常返回方法後執行
  • @AfterThrowing 異常返回通知 — 在連接點拋出異常後執行
  • @After 返回通知 — 方法最終結束後執行,相當於finaly
  • @Around 環繞通知 — 圍繞整個方法

五種類型執行順序為 @Around > @Before > @Around > @After > @AfterReturning

接下來我們直接介紹實作:

  1. 使用前面專案的 Todo 專案增加 AOP 方法,當使用者操作 Service 時,新增 Log 紀錄檔
    https://ithelp.ithome.com.tw/upload/images/20201006/20121179ENF3HjSkze.png

  2. 新增 ServiceAspect.kt 檔案,內容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    @Aspect
    @Component
    class ServiceAspect {
    // 第一個 * 表示任意返回值
    // com.inroman.demo... 為 package 路徑
    // 第二個 * 表示任何 Service 物件
    // 第三個 .*(..) 則表示任何方法
    @Pointcut("execution(* com.ironman.demo.service.*.*(..))")
    fun pointcut() {}

    // 設定 Before 通知並執行 pointcut 切點
    @Before("pointcut()")
    fun before(joinPoint: JoinPoint) {
    // 設定 Logger 帶入切入點類別名稱
    val logger = LoggerFactory.getLogger(joinPoint.target.javaClass.name)
    // 取得切入點方法
    val methodSignature: MethodSignature = joinPoint.signature as MethodSignature
    // 取得切入點方法名稱
    val methodName = methodSignature.method.name
    // 取得切入點方法類別
    val className = joinPoint.target.javaClass.name
    // 取得切入點方法參數
    val argsInfo = joinPoint.args
    logger.info("[處理開始] Service: $className, Method:$methodName, Args: $argsInfo")
    }

    // 設定 After 通知並執行 pointcut 切點
    @After("pointcut()")
    fun after(joinPoint: JoinPoint) {
    val logger = LoggerFactory.getLogger(joinPoint.target.javaClass.name)
    val methodSignature: MethodSignature = joinPoint.signature as MethodSignature
    val methodName = methodSignature.method.name
    val className = joinPoint.target.javaClass.name
    val argsInfo = joinPoint.args
    logger.info("[處理結束] Service: $className, Method: $methodName, Args: $argsInfo")
    }
    }
  3. 觀察專案運行 Log
    https://ithelp.ithome.com.tw/upload/images/20201006/20121179yWxARLJpF1.png

Reference