淺談Spring AOP 面向切面編程 最通俗易懂的畫圖理解AOP、AOP通知執行順序~


簡介

我們都知道,Spring 框架作為后端主流框架之一,最有特點的三部分就是IOC控制反轉、依賴注入、以及AOP切面。當然AOP作為一個Spring

的重要組成模塊,當然IOC是不依賴於Spring框架的,這就說明你有權選擇是否要用AOP來完成一些業務。

AOP面向切面編程,通過另一種思考的方式,來彌補面向對象編程OOP當中的不足,OOP當中最重要的單元是類,所以萬物皆對象,萬物皆是

對象類。而在AOP的模塊單元中,最基礎的單元是切面,切面對切點進行模塊化的管理。

最后再提一句:Spring當中的AOP是利用Java的代理模式實現的

 

AOP概念

讓我們從一些基礎的術語開始了解面向切面編程AOP,術語不是特別的直觀,最好的方式就是通過文本理解+圖像理解+代碼實例理解

這樣對於我們來說才是真正意義上的理解。

  • 切面:(Aspect) 一個關注點的模塊化,就比較籠統的一個概念,關注點可能橫切多個對象。若不理解請往后看圖片理解,對應的注解有@Aspect。
  • 連接點:(Joinpoint) 在程序執行過程中某個特定的點,一個連接點總是代表一個方法的執行。
  • 通知:(Advice) 通知表示在一個連接點執行的具體的動作,比如After Before 表明通知的具體動作
  • 切入點:(Pointcut)通過一個表達式去表明我所定義的通知在那個地點具體執行。
  • 前置通知:(Before advice)表明在連接點執行之前執行的動作。
  • 后置通知:(After returning advice)在某個連接點完成后的通知,比如一個方法沒有拋出任何異常,正常返回。
  • 環繞通知:(Around Advice) 環繞可以看作是包含前置通知和后置通知的一個通知,先了解,后面具體理解。
  • 異常通知:(After throwing advice) 在方法異常推出時候執行的通知。
  • 最終通知:(After advice) 在連接點退出時候執行的通知。不論是正常退出還是異常退出。

說了這么多,都感覺迷迷糊糊的,我們首先來看一個例子吧,通過這個例子來理解AOP切面,通過例子在具體說明

 

Springboot AOP

版本信息:Springboot 2.1.6

添加依賴 Web AOP

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

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

寫一個最簡單的控制器

@RestController
@RequestMapping("/")
public class AuthController {

    private Logger logger = LoggerFactory.getLogger(AuthController.class);

    @GetMapping("login")
    public String login(String user,String pass) {

        logger.info("login--->user",user);
        logger.info("login--->pass",pass);

        return "success";
    }

}

這樣的控制器在SpringBoot的Web開發當中是很常見的,收到前端的請求,將參數進行校驗,我們這里為了簡單,不作校驗,只是打印出來,為了配合我們后面的AOP的理解而

做的一個最簡單的控制器,測試請求以下路徑,控制台打印以下內容

http://localhost:8080/login?user=root&pass=root
2019-11-09 09:59:00.585  INFO 11100 --- [nio-8080-exec-1] c.e.demo.controller.AuthController       : login--->user
2019-11-09 09:59:00.588  INFO 11100 --- [nio-8080-exec-1] c.e.demo.controller.AuthController       : login--->pass

 

定義切面類

1、定義一個切面類,加入@Aspect注解和@Component注解

@Aspect
@Component
public class WebLogAsp {}

@Aspect 注解將找個類定義為一個切面對象,通過@Component注解將這個類對象注入到IOC容器,交給Spring來進行管理。

2、定義一個切入點 通過@Pointcut

    @Pointcut("execution(public * com.example.demo.controller.*.*(..))")
    public void controllerLog(){}

@Pointcut這個注解主要用來定義切入點,通過表達式的方式,來告訴Spring,我這個切點要切到什么位置,常用的就是execution去匹配連接點。

主要來說一下execution 匹配表達是的表達方法,我們按照以下的例子來說明:

@Pointcut("execution(public * com.example.demo.controller.*.*(..))")

語法:execution( [方法修飾符(可選)]__返回類型__類路徑__方法名__(參數)__[異常模式(可選)] )

這里我用下划線來代替空格,比較直觀的可以看出,我們這個例子里面,我將這個切點切入到com.example.demo.controller包下所有類的所有方法上面。

*就是通配符,(..)代表任意多個參數,也就說明我切入到的方法它的參數我是不限定的,可以有任意個參數。

3、定義通知項,@Before定義一個前置通知

@Before("controllerLog()")
public void beforeAdvice(JoinPoint joinPoint){}

@Before注解傳入字符串方法名,也就是我們上面定義的切入點的方法名,告訴它這個通知是在切入點 controllerLog()上面執行的通知,它會在切入點方法執行之前率先執行

這個方法傳入了一個JoinPoint 對象,也就是我們所說的連接點對象,連接點可以理解為切入點方法執行時候所產生的一個對象。

這里有幾個具體的方法很重要,需要說明一下:

  • Object[] getArgs();獲取切入點方法執行時候傳入的參數。
  • Object getTarget();獲取的是切入方法所在的類對象,簡單理解例子里面的切入點是login()方法,所以返回的對象就是AuthController對象
  • Object getThis();返回AOP框架為目標對象生成的代理對象。

這里將詳細代碼舉例,通過打印的方式進行輸出,通過SpringMvc的RequestContextHolder獲取線程的請求對象,這里我們可以拿到當前請求

的一些具體參數,比如訪問人的IP信息,它所請求的URL以及請求所帶的參數等待。

具體請參考:https://www.runoob.com/servlet/servlet-client-request.html

    @Before("controllerLog()")
    public void beforeAdvice(JoinPoint joinPoint){

        logger.info("前置通知開始--->");
        /*獲取當前請求的HttpServletRequest*/
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();

        logger.info("URL-->"+request.getRequestURL().toString());
        logger.info("IP-->"+request.getRemoteAddr());
        logger.info("HTTP_Method-->"+request.getMethod());
        logger.info("Request_args-->"+ Arrays.toString(joinPoint.getArgs()));

        logger.info("前置通知結束--->");
    }

4、環繞通知方法 @Around

首先來看一部分代碼,這里傳遞了一個 ProceedingJoinPoint 對象,它是JoinPoint 切點對象的一個子類,也可以為它是切點所在的方法執行時候所產生的一個對象

這里最重要的一個方法當屬於 proceed() ;執行proceed()就表示執行切點所在的方法執行,它會返回一個Object對象,也就是目標方法所返回的對象。

    @Around("controllerLog()")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        logger.info("環繞通知開始-->");
        Object result = null;

        /*proceed()方法表示連接點方法執行 result 為連接點方法的返回值*/
        result = proceedingJoinPoint.proceed();
        logger.info("環繞通知返回值-->" + result);

        logger.info("環繞通知結束-->");
        return result;
    }

踩坑:環繞方法一定要將result對象返回出去,若定義為void 無返回值的話,將在后面的@AfterReturning 后置通知中無法取到值!!!!

5、后置通知 @AfterReturning 連接點正常執行完畢后的通知

    @AfterReturning(value = "controllerLog()",returning = "obj")
    public void afterReturning(JoinPoint joinPoint,Object obj){
        logger.info("后置通知正常返回-->"+obj);
    }

這里傳遞了兩個參數,第一個參數還是指定切點,第二個參數指定的是返回值,這個返回值就是連接點所返回的參數值,需要在環繞通知里面將其返回出來才可以

取到值,不然返回的就是Null

6、異常通知 @AfterThrowing

    @AfterThrowing(value = "controllerLog()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint,Exception ex){
        logger.info("異常通知-->"+ex.getMessage());
    }

這里和上面的后置通知差不多,不過第二個參數是一個異常類型對象,可以取出發生異常時候的異常信息。

7、最終通知 @After

這個和@Before一樣,不再細說!

 

正常返回測試

 

這里可以很明確的看到,一個正常的返回路徑,相信我不說大家都可以看的清楚,畫圖最清楚的,接收到請求后,首先工作的是環繞通知,

環繞通知里面執行proceed()方法后,才會進入連接點方法執行,Before是在連接點方法執行之前所執行的,而后執行連接點方法內容,

正常執行完后,不發生異常情況,環繞通知結束,就會執行最終通知After After執行完畢后,才會執行后置通知AfterReturning

 

異常情況測試返回

我們在控制器里面模擬進行一個異常的拋出,看一下執行的順序 

        logger.info("將要拋出異常");
        int a = 1/0;

 

可以很明明顯的看到,我們在控制器里面通過一個除0的操作,進行拋出一個異常,這里環繞通知是沒有執行完畢的,因為拋出異常,停止了運行

從接收到請求開始,進入環繞通知,然后環繞通知里面調用了proceed()方法,其實就是讓連接點的方法開始運行,這時候前置通知首先跑起來,可以看到

前置通知是完完全全走完的。前置通知完畢后,下來是連接點的方法運行起來了,這里因為拋出了異常,沒有進行捕獲,最終通知還是一樣正常執行,不過最后執行的是異常的通知,

而不是像上面一樣。這里不同的地方還是要多進行理解。

 

畫圖理解

1、理解切面、切入點、連接點、通知、目標對象

 

2、理解通知點正常與異常執行順序

小結

通過對SpringAOP框架的研究,以及畫圖的理解,能夠更深刻的理解里面運行時候的內層含義,以及AOP的一些應用,比如系統里面的日志系統,通過學習AOP

完完全全可以很輕松的通過切面,將需要記錄的日志信息存到數據庫, 節約大量時間。

參考:

https://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/aop-api.html

http://shouce.jb51.net/spring/aop.html

https://www.cnblogs.com/wangshen31/p/9379197.html

代碼:

https://gitee.com/mrc1999/Springboot-aop

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM