欢迎关注微信公众号: 小哈学Java

文章首发于个人网站 https://www.exception.site/springboot/spring-boot-aop-web-request

本节中,您将学习如何在 Spring Boot 2.0 中使用 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 文件中添加依赖:

<!-- aop 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency> <!-- 用于日志切面中,以 json 格式打印出入参(本来使用阿里的 FASTJSON, 但是对于文件上传的接口,打印参数会报错,换为 Gson) -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>

三、配置 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

欢迎关注公众号: 小哈学Java

Spring Boot 2.0 教程 | AOP 切面统一打印请求日志的更多相关文章

  1. Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志

    其实,小哈在之前就出过一篇关于如何使用 AOP 切面统一打印请求日志的文章,那为什么还要再出一篇呢?没东西写了? 哈哈,当然不是!原因是当时的实现方案还是存在缺陷的,原因如下: 不够灵活,由于是以所有 ...

  2. 如何优雅地在 Spring Boot 中使用自定义注解,AOP 切面统一打印出入参日志 | 修订版

    欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 资深架构师吐血总结的 <Java 核心知识整理&面试.pdf>资源链接!! 个人网站: https://www.ex ...

  3. Spring Boot 2.0 教程 - 深入SpringAplication

    原文连接:https://www.codemore.top/cates/Backend/post/2018-05-20/spring-boot-SpringApplication 可以通过Spring ...

  4. Spring Boot 2.0 教程 | @ModelAttribute 注解

    欢迎关注微信公众号: 小哈学Java 文章首发于个人网站: https://www.exception.site/springboot/spring-boot-model-attribute Spri ...

  5. Spring Boot 2.0 教程 | 配置 Undertow 容器

    欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 资深架构师吐血总结的 <Java 核心知识整理&面试.pdf>资源链接!! 文章首发于个人网站 https://ww ...

  6. Spring Boot 2.0 教程 - 配置详解

    Spring Boot 可以通过properties文件,YAML文件,环境变量和命令行参数进行配置.属性值可以通过,@Value注解,Environment或者ConfigurationProper ...

  7. Spring Boot 2.0 教程 | 快速集成整合消息中间件 Kafka

    欢迎关注个人微信公众号: 小哈学Java, 每日推送 Java 领域干货文章,关注即免费无套路附送 100G 海量学习.面试资源哟!! 个人网站: https://www.exception.site ...

  8. Spring Boot 2.0 图文教程 | 集成邮件发送功能

    文章首发自个人微信公众号: 小哈学Java 个人网站: https://www.exception.site/springboot/spring-boots-send-mail 大家好,后续会间断地奉 ...

  9. Spring Boot 2.0 的快速入门(图文教程)

    摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! Spring Boot 2.0 的快速入门(图文教程) 大家都 ...

随机推荐

  1. mysql驱动问题

    Unknown initial character set index '255' received from server. Initial client character 解决方法 驱动版本不对 ...

  2. java 模拟浏览器爬虫

  3. Win10企业版永久激活方法(cmd命令)

    Win10企业版永久激活工具请点击这里哦>>>>>Win10企业版永久激活工具<<<<< Win10企业版永久激活方法: 1.右击桌面的左下 ...

  4. CICD - Teamcity 配置之二:SSIS Package 自动部署

    首先需要将SSIS Project check in 到 git 中, Teamcity 会到 git 上取到code, build, 然后publish 到指定的服务器上,比如:Dev, QA, S ...

  5. 【C语言编程练习】5.11 猴子吃桃子问题

    1. 问题描述 有一只猴子,第一天摘下若干个桃子,当即吃掉了一半,又多吃了一个,第二天又将剩下的桃子吃掉了一半,又多吃了一个,按照这样的吃法,每天都吃前一天吃下的桃子的一半又一个.到第十天,就剩下了一 ...

  6. 英语词汇—V01

    今日词汇 1, wash [wɒʃ] n. 洗涤:洗的衣服:化妆水:冲积物 vt. 洗涤:洗刷:冲走:拍打 vi. 洗澡:被冲蚀 2, dust [dʌst] n. 灰尘:尘埃:尘土 vt. 撒:拂去 ...

  7. 我的 FPGA 学习历程(11)—— 实验:按键消抖

    按键是一个输入设备,在理论上可以归为开关一类,理想的按键波形如下: 然而由于按键的机械特性,断开和闭合动作是不可能在一瞬间完成的,实际的波形如下: 抖动期间电平处于临界值,由于晶振的频率相当的高,数字 ...

  8. 操作redis数据库 & 操作Excel & 开发接口

    操作redis数据库: string类型 1. 增 set,传俩个参数 key value(只要是字符串就行)2. 删 delete 传一个参数 key3. 修改 set 在目标key重新传参 key ...

  9. centos7搭建zabbix3.0监控系统

    关闭防火墙和selinux systemctl stop firewalld.service                (停止防火墙) systemctl disable firewalld.se ...

  10. 判断DataTale中判断某个字段中包含某个数据

    // <summary> /// 判断DataTale中判断某个字段中包含某个数据 /// </summary> /// <param name="dt&quo ...