1. 程式人生 > >spring boot 中AOP的使用

spring boot 中AOP的使用

一、AOP統一處理請求日誌

也談AOP

1、AOP是一種程式設計正規化

2、與語言無關,是一種程式設計思想

  • 面向切面(AOP)Aspect Oriented Programming
  • 面向物件(OOP)Object Oriented Programming
  • 面向過程(POP) Procedure Oriented Programming

 

再談AOP

1、面向過程到面向物件

2、換個角度看世界,換個姿勢處理問題 

3、將通用邏輯從業務邏輯中分離出來

 

二、處理過程

個人理解,其實就是日誌體系為了方便使用,可以用log4j的思維去理解下。

 

三、Aop的實際應用

1、準備工作

在pom中新增aop依賴,具體示例如下:

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

2、日誌輸出

比如我們在實際專案中,期望我們操作的每一步都有日誌輸出,那麼我們該怎麼做呢,還是用前面學生資訊的原始碼,來進行演示。

首先建立一個切面,這裡和spring中的aop其實都一樣的,可以說是更簡便了,具體示例程式碼如下:

package com.rongrong.springboot.demo.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @author rongrong
 * @version 1.0
 * @description:
 * @date 2020/1/6 21:50
 */
@Aspect
@Component
public class HttpAspect {

    @Before("execution(public * com.rongrong.springboot.demo.controller.StudentController.*(..))")
    public void log(){
        System.out.println("我執行了!!");
    }
}

接著啟動專案,呼叫下查詢介面,控制檯輸出如下圖列印內容,證明成功

3、日誌輸出程式碼優化

有before,肯定就會有after,即呼叫時有日誌輸出,呼叫完也有結果輸出,一來方便自己除錯,二來也方便檢視報錯,那麼after怎麼寫呢?我猜一般同學肯定都這麼幹。

package com.rongrong.springboot.demo.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @author rongrong
 * @version 1.0
 * @description:
 * @date 2020/1/6 21:50
 */
@Aspect
@Component
public class HttpAspect {

    @Before("execution(public * com.rongrong.springboot.demo.controller.StudentController.*(..))")
    public void log(){
        System.out.println("我執行了!!");
    }

    @After("execution(public * com.rongrong.springboot.demo.controller.StudentController.*(..))")
    public void afterlog(){
        System.out.println("我執行了!!");
    }
}

這樣寫一點毛病也沒有,但是。。。。。。。。。。。。。。。。。。。。哇哈哈哈哈哈哈,你肯定會說,我肯定不這樣寫,可以不承認,但有些同學肯定是這樣乾的。

寫程式碼的原則,儘量少寫重複程式碼,為啥呢?維護成本高呀,再就是讓人覺得你的程式碼很low逼,看到這你肯定不會那麼幹了吧,哈哈哈哈哈。

好了玩笑開完了,我們可以這樣,宣告個切點,再切點裡維護想要的切面,具體程式碼示例如下:

package com.rongrong.springboot.demo.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @author rongrong
 * @version 1.0
 * @description:
 * @date 2020/1/6 21:50
 */
@Aspect
@Component
public class HttpAspect {

    @Pointcut("execution(public * com.rongrong.springboot.demo.controller.StudentController.*(..))")
    public void log(){
    }
    
    @Before("log()")
    public void doBefore(){
        System.out.println("我執行了!!");
    }
    
    @After("log()")
    public void doAfter(){
        System.out.println("我執行了!!");
    }
}

4、Self4j的使用

改了下,發現似乎還是有點low,都用spring boot框架了,咋還能用System.out.println()輸出呢,那麼怎麼優化呢?

現在僅僅滿足了,控制檯輸出內容,但是如果我想要的日誌不是這樣的,最基本的得上面一樣吧,有日期、埠、方法名之類的,即專案啟動時控制檯這樣的日誌才好看些吧,使用spring boot框架自帶日誌self4j即可解決,具體程式碼示例如下:

package com.rongrong.springboot.demo.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @author rongrong
 * @version 1.0
 * @description:
 * @date 2020/1/6 21:50
 */
@Aspect
@Component
public class HttpAspect {

   /**
     * 使用self4j,此日誌為spring boot自帶的日誌框架
     */
    private final static Logger logger= LoggerFactory.getLogger(HttpAspect.class);

    @Pointcut("execution(public * com.rongrong.springboot.demo.controller.StudentController.*(..))")
    public void log(){
    }
    
    @Before("log()")
    public void doBefore(){
        logger.info("我執行了!!");
    }
    
    @After("log()")
    public void doAfter(){
        logger.info("我執行了!!");
    }
}

啟動專案後,如下圖所示,證明我們成功了

5、請求引數及響應資訊控制檯輸出

這似乎看起來好了很多,但是實際工作時候,為了方便除錯需要把我們請求介面及請求後返回的響應資訊,在控制檯輸出,方便我們除錯定位問題,下面我們來進行演示如何從控制檯輸出這些資訊。

5.1、輸出請求引數資訊

使用RequestContextHolder來獲得請求引數相關屬性,這裡需要強轉成ServletRequestAttributes物件,Joinpoint這個引數非必須,是在獲取“類方法”、“類名”、“方法引數”的時候會用到,如果用不到的話就不需要了。

具體示例程式碼如下所示:

package com.rongrong.springboot.demo.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.jws.Oneway;
import javax.servlet.http.HttpServletRequest;

/**
 * @author rongrong
 * @version 1.0
 * @description:
 * @date 2020/1/6 21:50
 */
@Aspect
@Component
public class HttpAspect {

    /**
     * 使用self4j,此日誌為spring boot自帶的日誌框架
     */
    private final static Logger logger= LoggerFactory.getLogger(HttpAspect.class);

    /**
     *此處為了簡化程式碼,提高維護性,還是需要提煉下的
    @Before("execution(public * com.rongrong.springboot.demo.controller.StudentController.*(..))")
    public void log(){
        System.out.println("我執行了!!");
    }
     */

    @Pointcut("execution(public * com.rongrong.springboot.demo.controller.StudentController.*(..))")
    public void log(){
    }

    /**
     * 在介面執行操作時輸出相關引數
     */
    @Before("log()")
    public void doBefore(JoinPoint joinPoint){
        //使用RequestContextHolder來獲得請求屬性,這裡需要強轉成ServletRequestAttributes物件
        ServletRequestAttributes servletRequestAttributes= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //獲取請求url
        String url = servletRequestAttributes.getRequest().getRequestURI();
        //獲取請求IP
        String addr = servletRequestAttributes.getRequest().getRemoteAddr();
        //獲取請求方法
        String method = servletRequestAttributes.getRequest().getMethod();
        //獲取類名
        String pCName = joinPoint.getSignature().getDeclaringTypeName();
        //獲取類方法
        String cName = joinPoint.getSignature().getName();
        //這裡要說明下 logger.info("url={}",url),url為{}自動填充部分
        //url
        logger.info("url= {}",url);
        //ip
        logger.info("ip= {}",addr);
        //method
        logger.info("method= {}",method);
        //args
        //獲取請求引數
        logger.info("args= {}",joinPoint.getArgs());
        //類名和類方法
        logger.info("類名和類方法= {}",pCName+"."+cName);
    }

    @After("log()")
    public void doAfter(){
        logger.info("doAfter :我執行了!!");
    }

}

重新啟動專案,我們呼叫下查詢所有學生介面,控制檯顯示如下資訊,證明日誌成功!

5.2、輸出響應資訊

接下來我們再來輸出響應結果資訊,使用註解@AfterReturning,獲取返回相應資訊,具體示例程式碼如下:

package com.rongrong.springboot.demo.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.jws.Oneway;
import javax.servlet.http.HttpServletRequest;

/**
 * @author rongrong
 * @version 1.0
 * @description:
 * @date 2020/1/6 21:50
 */
@Aspect
@Component
public class HttpAspect {

    /**
     * 使用self4j,此日誌為spring boot自帶的日誌框架
     */
    private final static Logger logger= LoggerFactory.getLogger(HttpAspect.class);

    /**
     *此處為了簡化程式碼,提高維護性,還是需要提煉下的
    @Before("execution(public * com.rongrong.springboot.demo.controller.StudentController.*(..))")
    public void log(){
        System.out.println("我執行了!!");
    }
     */

    @Pointcut("execution(public * com.rongrong.springboot.demo.controller.StudentController.*(..))")
    public void log(){
    }

    /**
     * 在介面執行操作時輸出相關引數
     */
    @Before("log()")
    public void doBefore(JoinPoint joinPoint){
        //使用RequestContextHolder來獲得請求屬性,這裡需要強轉成ServletRequestAttributes物件
        ServletRequestAttributes servletRequestAttributes= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        //獲取請求url
        String url = servletRequestAttributes.getRequest().getRequestURI();
        //獲取請求IP
        String addr = servletRequestAttributes.getRequest().getRemoteAddr();
        //獲取請求方法
        String method = servletRequestAttributes.getRequest().getMethod();
        //獲取類名
        String pCName = joinPoint.getSignature().getDeclaringTypeName();
        //獲取類方法
        String cName = joinPoint.getSignature().getName();
        //這裡要說明下 logger.info("url={}",url),url為{}自動填充部分
        //url
        logger.info("url= {}",url);
        //ip
        logger.info("ip= {}",addr);
        //method
        logger.info("method= {}",method);
        //args
        //獲取請求引數
        logger.info("args= {}",joinPoint.getArgs());
        //類名和類方法
        logger.info("類名和類方法= {}",pCName+"."+cName);
    }

    @After("log()")
    public void doAfter(){
        logger.info("doAfter :我執行了!!");
    }

    /**
     * 使用@AfterReturning,獲取返回相應資訊
     */
    @AfterReturning(returning = "object",pointcut="log()")
    public void doAfterReturning(Object object){
        logger.info("返回資訊 :{}",object.toString());
    }
}

再次重新啟動專案,我們呼叫下查詢所有學生介面,控制檯顯示如下資訊,證明日誌成功!

到此,spring boot中Aop的使用分享完畢,有興趣的同學可以自行嘗試哦。