Spring Boot AOP 切面統一打印請求與響應日志


原文:https://blog.csdn.net/weiwosuoai/article/details/100032090

 

本節中,您將學習如何在 Spring Boot 中使用 AOP 切面統一處理請求日志,打印進出參相關參數。

一、先看看日志輸出效果


可以看到,每個對於每個請求,開始與結束一目了然,並且打印了以下參數:

URL: 請求接口地址;

HTTP Method: 請求的方法,是 POST, GET, 還是 DELETE 等;

Class Method: 對應 Controller 的全路徑以及調用的哪個方法;

IP: 請求 IP 地址;

Request Args: 請求入參,以 JSON 格式輸出;

Response Args: 響應出參,以 JSON 格式輸出;

Time-Consuming: 請求耗時;

效果應該還不錯吧!接下來就讓我們一步一步去實現該功能, 首先,新建一個 Spring Boot Web 項目。

二、添加 Maven 依賴
在項目 pom.xml 文件中添加依賴:


spring-boot-starter-aop

com.google.code.gson

spring-boot-starter-aop

 

三、配置 AOP 切面
在配置 AOP 切面之前,我們需要了解下 aspectj 相關注解的作用:

@Aspect:聲明該類為一個注解類;

@Pointcut:定義一個切點,后面跟隨一個表達式,表達式可以定義為某個 package 下的方法,也可以是自定義注解等;

切點定義好后,就是圍繞這個切點做文章了:

@Before: 在切點之前,織入相關代碼;

@After: 在切點之后,織入相關代碼;

@AfterReturning: 在切點返回內容后,織入相關代碼,一般用於對返回值做些加工處理的場景;

@AfterThrowing: 用來處理當織入的代碼拋出異常后的邏輯處理;

@Around: 在切入點前后織入代碼,並且可以自由的控制何時執行切點;

接下來,定義一個 WebLogAspect.java 切面類,代碼如下:

 

package site.exception.springbootaopwebrequest.aspect;    
import com.google.gson.Gson;    
import org.aspectj.lang.JoinPoint;    
import org.aspectj.lang.ProceedingJoinPoint;    
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.servlet.http.HttpServletRequest;    
/**    
 * @author www.exception.site (exception 教程網)    
 * @date 2019/2/12    
 * @time 14:03    
 * @discription    
 **/    
@Aspect    
@Component    
public class WebLogAspect {    
    private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);    
    /** 以 controller 包下定義的所有請求為切入點 */    
    @Pointcut("execution(public * site.exception.springbootaopwebrequest.controller..*.*(..))")    
    public void webLog() {}    
    /**    
     * 在切點之前織入    
     * @param joinPoint    
     * @throws Throwable    
     */    
    @Before("webLog()")    
    public void doBefore(JoinPoint joinPoint) throws Throwable {    
        // 開始打印請求日志    
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();    
        HttpServletRequest request = attributes.getRequest();    
        // 打印請求相關參數    
        logger.info("========================================== Start ==========================================");    
        // 打印請求 url    
        logger.info("URL            : {}", request.getRequestURL().toString());    
        // 打印 Http method    
        logger.info("HTTP Method    : {}", request.getMethod());    
        // 打印調用 controller 的全路徑以及執行方法    
        logger.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());    
        // 打印請求的 IP    
        logger.info("IP             : {}", request.getRemoteAddr());    
        // 打印請求入參    
        logger.info("Request Args   : {}", new Gson().toJson(joinPoint.getArgs()));    
    }    
    /**    
     * 在切點之后織入    
     * @throws Throwable    
     */    
    @After("webLog()")    
    public void doAfter() throws Throwable {    
        logger.info("=========================================== End ===========================================");    
        // 每個請求之間空一行    
        logger.info("");    
    }    
    /**    
     * 環繞    
     * @param proceedingJoinPoint    
     * @return    
     * @throws Throwable    
     */    
    @Around("webLog()")    
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {    
        long startTime = System.currentTimeMillis();    
        Object result = proceedingJoinPoint.proceed();    
        // 打印出參    
        logger.info("Response Args  : {}", new Gson().toJson(result));    
        // 執行耗時    
        logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);    
        return result;    
    }    
}

我們通過 @Aspect 聲明了 WebLogAspect.java 為切面類,之后,通過 @Pointcut 定義了打印請求日志的切點,切點為 site.exception.springbootaopwebrequest.controller 包下所有的請求接口。

切點定義好后,我們通過 @Before 在切點之前打印請求的相關參數,通過 @Around 打印了請求接口的耗時時間,最后通過 @After 做了請求的收尾工作。

到這里,切面相關的代碼就完成了!

三、測試
我們針對 GET, POST, 文件提交,以及多文件提交四種接口分別測試其效果, 接口定義如下:

package site.exception.springbootaopwebrequest.controller;    
import org.slf4j.Logger;    
import org.slf4j.LoggerFactory;    
import org.springframework.web.bind.annotation.*;    
import org.springframework.web.multipart.MultipartFile;    
import site.exception.springbootaopwebrequest.entity.User;    
/**    
 * @author www.exception.site (exception 教程網)    
 * @date 2019/2/16    
 * @time 21:03    
 * @discription    
 **/    
@RestController    
public class TestController {    
    private final static Logger logger = LoggerFactory.getLogger(TestController.class);    
    /**    
     * POST 方式接口測試    
     * @param user    
     * @return    
     */    
    @PostMapping("/user")    
    public User testPost(@RequestBody User user) {    
        logger.info("testPost ...");    
        return user;    
    }    
    /**    
     * GET 方式接口測試    
     * @return    
     */    
    @GetMapping("/user")    
    public String testGet(@RequestParam("username") String username,    
                          @RequestParam("password") String password) {    
        logger.info("testGet ...");    
        return "success";    
    }    
    /**    
     * 單文件上傳接口測試    
     * @return    
     */    
    @PostMapping("/file/upload")    
    public String testFileUpload(@RequestParam("file") MultipartFile file) {    
        logger.info("testFileUpload ...");    
        return "success";    
    }    
    /**    
     * 多文件上傳接口測試    
     * @return    
     */    
    @PostMapping("/multiFile/upload")    
    public String testMultiFileUpload(@RequestParam("file") MultipartFile[] file) {    
        logger.info("testMultiFileUpload ...");    
        return "success";    
    }    
}

 

User.java:

package site.exception.springbootaopwebrequest.entity;    
import java.io.Serializable;    
import java.util.Date;    
/**    
 * @author www.exception.site (exception 教程網)    
 * @date 2019/2/16    
 * @time 21:00    
 * @discription    
 **/    
public class User implements Serializable {    
    /**    
     * 用戶名    
     */    
    private String username;    
    /**    
     * 密碼    
     */    
    private String password;    
    /**    
     * 創建時間    
     */    
    private Date createTime;    
    public String getUsername() {    
        return username;    
    }    
    public void setUsername(String username) {    
        this.username = username;    
    }    
    public String getPassword() {    
        return password;    
    }    
    public void setPassword(String password) {    
        this.password = password;    
    }    
    public Date getCreateTime() {    
        return createTime;    
    }    
    public void setCreateTime(Date createTime) {    
        this.createTime = createTime;    
    }    
}

 

3.1 GET 接口測試

請求 http://localhost:8080/user?username=張三&password=123456接口,觀察日志打印情況:

 

 

GET 接口正常打印日志!

3.2 POST 接口測試
通過 Postman 請求 http://localhost:8080/user POST 接口:

 

 

 

看看控制台輸出效果:

 

 

 

POST 接口也是 OK 的!

3.3 單文件提交接口測試
請求單文件提交接口: http://localhost:8080/file/upload :

 

 

 

日志輸出如下:

 

 

 

3.4 多文件提交接口測試
請求單文件提交接口: http://localhost:8080/multiFile/upload :

 

 

 

切面日志輸出:

 

 

 

為何不用 FASTJSON
筆者在開始階段的確使用的是阿里 FASTJSON 來做出入參的打印,但是對於文件上傳接口,FASTJSON 轉換會出錯,故改用谷歌的 Gson。

 

 

 

GitHub 源碼地址
https://github.com/weiwosuoai/spring-boot-tutorial/tree/master/spring-boot-aop-web-request

·END· 

 


免責聲明!

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



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