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

簡介

咱們都知道,Spring 框架做爲後端主流框架之一,最有特色的三部分就是IOC控制反轉、依賴注入、以及AOP切面。固然AOP做爲一個Springhtml

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

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

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

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

 

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.6api

添加依賴 Web AOPapp

        <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

 

定義切面類

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

@Aspect
@Component
public class WebLogAsp {}

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

二、定義一個切入點 經過@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包下全部類的全部方法上面。

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

三、定義通知項,@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("前置通知結束--->");
    }

四、環繞通知方法 @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 後置通知中沒法取到值!!!!

五、後置通知 @AfterReturning 鏈接點正常執行完畢後的通知

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

這裏傳遞了兩個參數,第一個參數仍是指定切點,第二個參數指定的是返回值,這個返回值就是鏈接點所返回的參數值,須要在環繞通知裏面將其返回出來才能夠

取到值,否則返回的就是Null

六、異常通知 @AfterThrowing

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

這裏和上面的後置通知差很少,不過第二個參數是一個異常類型對象,能夠取出發生異常時候的異常信息。

七、最終通知 @After

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

 

正常返回測試

 

這裏能夠很明確的看到,一個正常的返回路徑,相信我不說你們均可以看的清楚,畫圖最清楚的,接收到請求後,首先工做的是環繞通知,

環繞通知裏面執行proceed()方法後,纔會進入鏈接點方法執行,Before是在鏈接點方法執行以前所執行的,然後執行鏈接點方法內容,

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

 

異常狀況測試返回

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

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

 

能夠很明明顯的看到,咱們在控制器裏面經過一個除0的操做,進行拋出一個異常,這裏環繞通知是沒有執行完畢的,由於拋出異常,中止了運行

從接收到請求開始,進入環繞通知,而後環繞通知裏面調用了proceed()方法,其實就是讓鏈接點的方法開始運行,這時候前置通知首先跑起來,能夠看到

前置通知是完徹底全走完的。前置通知完畢後,下來是鏈接點的方法運行起來了,這裏由於拋出了異常,沒有進行捕獲,最終通知仍是同樣正常執行,不過最後執行的是異常的通知,

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

 

畫圖理解

一、理解切面、切入點、鏈接點、通知、目標對象

 

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

小結

經過對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

http://www.javashuo.com/article/p-riqaztfy-gq.html

代碼:

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

相關文章
相關標籤/搜索