1. 程式人生 > 其它 >SpringBoot - 面向切面程式設計 AOP 的配置和使用

SpringBoot - 面向切面程式設計 AOP 的配置和使用

技術標籤:springbootaopspring boot

一、基本介紹
1,什麼是 AOP
(1)AOP 為 Aspect Oriented Programming 的縮寫,意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。
(2)利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
一個 AOP 的使用場景:
假設一個已經上線的系統執行出現問題,有時執行得很慢。為了檢測出是哪個環節出現了問題,就需要監控每一個方法的執行時間,再根據執行時間進行分析判斷。
由於整個系統裡的方法數量十分龐大,如果一個個方法去修改工作量將會十分巨大,而且這些監控方法在分析完畢後還需要移除掉,所以這種方式並不合適。

如果能夠在系統執行過程中動態新增程式碼,就能很好地解決這個需求。這種在系統執行時動態新增程式碼的方式稱為面向切面程式設計(AOP)

2,AOP 相關概念介紹
Joinpoint(連線點):類裡面可以被增強的方法即為連線點。例如,想要修改哪個方法的功能,那麼該方法就是一個連結點。
Target(目標物件):要增強的類成為 Target。
Pointcut(切入點):對 Jointpoint 進行攔截的定義即為切入點。例如,攔截所有以 insert 開始的方法,這個定義即為切入點。
Advice(通知):攔截到 Jointpoint 之後要做的事情就是通知。通知分為前置通知、後置通知、異常通知、最終通知和環繞通知。例如,前面說到的列印日誌監控就是通知。

Aspect(切面):即 Pointcut 和 Advice 的結合。

3,安裝配置
Spring Boot 在 Spring 的基礎上對 AOP 的配置提供了自動化配置解決方案,我們只需要修改 pom.xml 檔案,新增 spring-boot-starter-aop 依賴即可。

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

二、使用樣例
1,建立 Service
首先建立一個 UserService(假設在 com.example.demo.service),內容如下:

package com.example.demo.service;
public interface UserService {
    String getUserById(Integer id);
}

再建立一個 UserServiceImpl(假設在 com.example.demo.service.impl),內容如下:

package com.example.demo.service.impl;

import com.example.demo.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    @Override
    public String getUserById(Integer id) {
        System.out.println("getUserById(" + id + ")...");
        // 等待2秒
        try {
            Thread.sleep(2000);
        }
        catch(InterruptedException e) {
            e.printStackTrace();
        }
        return "";
    }
}

2,建立切面
接著定義一個切面類,程式碼如下:

註解說明:
(1)@Aspect 註解:表明這是一個切面類。
(2)@Pointcut 註解:表明這是一個切入點。
execution 中的第一個 * 表示方法返回任意值
第二個 * 表示 service 包下的任意類
第三個 * 表示類中的任意方法,括號中的兩個點表示方法引數任意,即這裡描述的切入點為 service 包下所有類中的所有方法。
(3)@Before 註解:表示這是一個前置通知,該方法在目標方法之前執行。
通過 JoinPoint 引數可以獲取目標方法的方法名、修飾符等資訊。
(4)@After 註解:表示這是一個後置通知,該方法在目標執行之後執行。
(5)@AfterReturning 註解:表示這是一個返回通知,在該方法中可以獲取目標方法的返回值。
returning 引數是指返回值的變數名,對應方法的引數。
注意:本樣例在方法引數中定義 result 的型別為 Object,表示目標方法的返回值可以是任意型別。若 result 引數的型別為 Long,則該方法只能處理目標方法返回值為 Long 的情況。
(6)@AfterThrowing 註解:表示這是一個異常通知,即當目標方法發生異常,該方法會被呼叫。
樣例中設定的異常型別為 Exception 表示所有的異常都會進入該方法中執行。
若異常型別為 ArithmeticException 則表示只有目標方法丟擲的 ArithmeticException 異常才會進入該方法的處理。
(7) @Around 註解:表示這是一個環繞通知。環繞通知是所有通知裡功能最為強大的通知,可以實現前置通知、後置通知、異常通知以及返回通知的功能。
目標方法進入環繞通知後,通過呼叫 ProceedingJointPoint 物件的 proceed 方法使目標方法繼續執行,開發者可以在次修改目標方法的執行引數、返回值值,並且可以在此目標方法的異常。

package com.example.demo.point;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogAspect {
    // 定義一個切入點
    @Pointcut("execution(* com.example.demo.service.*.*(..))")
    public void pc1(){

    }

    // 前置通知
    @Before(value = "pc1()")
    public void before(JoinPoint jp) {
        String name = jp.getSignature().getName();
        System.out.println(name + "方法開始執行...");
    }

    // 後置通知
    @After(value = "pc1()")
    public void after(JoinPoint jp) {
        String name = jp.getSignature().getName();
        System.out.println(name + "方法執行結束...");
    }

    // 返回通知
    @AfterReturning(value = "pc1()", returning = "result")
    public void afterReturning(JoinPoint jp, Object result) {
        String name = jp.getSignature().getName();
        System.out.println(name + "方法返回值為:" + result);
    }

    // 異常通知
    @AfterThrowing(value = "pc1()", throwing = "e")
    public void afterThrowing(JoinPoint jp, Exception e) {
        String name = jp.getSignature().getName();
        System.out.println(name + "方法拋異常了,異常是:" + e.getMessage());
    }

    // 環繞通知
    @Around("pc1()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        String name = pjp.getSignature().getName();
        // 統計方法執行時間
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        long end = System.currentTimeMillis();
        System.out.println(name + "方法執行時間為:" + (end - start) + " ms");
        return result;
    }
}

3,建立 LogAspectController
配置完成後,接下來在 LogAspectController中建立介面呼叫 UserService 中的方法。

package com.example.demo.controller;

import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LogAspectController {
    @Autowired
    UserService userService;
        /**
         * http://localhost:8080/LogAspect?id=100
        * */
    @GetMapping("/LogAspect")
    public String test(Integer id) {
        return userService.getUserById(id);
    }
}

4,執行樣例
(1)使用瀏覽器訪問如下地址:
http://localhost:8080/LogAspect?id=100
(2)檢視控制檯資訊,可以發現 LogAspect 中的程式碼動態地嵌入目標方法中執行了。
在這裡插入圖片描述