通过自定义注解的方式,在 Spring Boot 中实现 AOP 切面统一打印出入参日志。
步骤
1.定义自定义注解。
2.编写AOP切面方法。
3.在需要输出日志的方法上使用注解。
代码
maven依赖
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.9</version>
</dependency>
<!-- commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
自定义注解
package com.example.practise.annotation;
import java.lang.annotation.*;
/**
* description: 注解 打印请求及应答信息
* date: 2022/03/21 15:38
* author: LenonJin
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface WebLog {
/**
* 方法描述
*/
String description() default "";
/**
* 是否打印request
*/
boolean printReq() default true;
/**
* 是否打印response
*/
boolean printRes() default true;
/**
* 是否脱敏request
*/
boolean sensitiveReq() default false;
/**
* 是否脱敏response
*/
boolean sensitiveRes() default false;
}
AOP切面
package com.example.practise.aspectj;
import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONUtil;
import com.example.practise.annotation.WebLog;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* description: 切面 打印请求及应答信息
* date: 2022/03/21 14:37
* author: LenonJin
*/
@Slf4j
@Aspect
@Component
public class WebLogAspect {
/**
* 切点
*/
@Pointcut("@annotation(com.example.practise.annotation.WebLog)")
public void pointCut() {
}
/**
* description: 环绕切面
* date: 2022/03/21 14:45
* author: LenonJin
*
* @param proceedingJoinPoint
* @param logPrint
* @return java.lang.Object
*/
@Around("pointCut() && @annotation(logPrint)")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint, WebLog logPrint) throws Throwable {
log.info("----------------------------------------------------------- Start -----------------------------------------------------------");
// 耗时情况
long startTime = System.currentTimeMillis();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 请求参数是否打印及脱敏
String requestStr = "";
requestStr = logPrint.printReq() ? getRequestArgs(request, proceedingJoinPoint) : "请求参数打印关闭.";
requestStr = logPrint.sensitiveReq() ? sensitive(requestStr) : requestStr;
// 打印请求信息
log.info("Request Uri :{}", request.getRequestURI());
log.info("Request Ip :{}", StringUtils.isBlank(request.getHeader("X-Real-IP")) ? request.getRemoteAddr() : request.getHeader("X-Real-IP"));
log.info("Http Method :{}", request.getMethod());
log.info("Description :{}", logPrint.description());
log.info("Class Method :{}.{}", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName());
log.info("Request Args :{}", requestStr);
Object result = "";
result = proceedingJoinPoint.proceed();
// 应答参数是否打印及脱敏
String responseStr = "";
responseStr = logPrint.printRes() ? JSONUtil.toJsonStr(result) : "应答参数打印关闭.";
responseStr = logPrint.sensitiveRes() ? sensitive(responseStr) : responseStr;
// 打印应答信息
log.info("Response Args :{}", responseStr);
log.info("Time Consuming :{} ms", System.currentTimeMillis() - startTime);
log.info("------------------------------------------------------------ End ------------------------------------------------------------");
return result;
}
/**
* description: 判断请求类型
* date: 2022/03/21 14:45
* author: LenonJin
*
* @param request
* @return boolean
*/
private boolean isJsonRequest(HttpServletRequest request) {
String contentType = request.getContentType();
if (contentType != null) {
return StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE);
}
return false;
}
/**
* description: 获取请求参数
* date: 2022/11/17 14:45
* author: LenonJin
*
* @param request
* @param joinPoint
* @return java.lang.String
*/
String getRequestArgs(HttpServletRequest request, JoinPoint joinPoint) {
// 获取请求参数
if (isJsonRequest(request)) {
// ServletRequest、ServletResponse、MultipartFile 不能被序列化
Object[] args = joinPoint.getArgs();
if (ArrayUtils.isNotEmpty(args)) {
List<Object> logArgs = Arrays.stream(args).filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
.collect(Collectors.toList());
return JSONUtil.toJsonStr(logArgs);
} else {
return "args is null.";
}
} else {
Map<String, String[]> parameterMap = request.getParameterMap();
if (MapUtil.isNotEmpty(parameterMap)) {
String parameters = JSONUtil.toJsonStr(parameterMap);
return parameters;
} else {
String parameters = StringUtils.isBlank(request.getQueryString()) ? "args is null." : request.getQueryString();
return parameters;
}
}
}
/**
* description: 字符串脱敏处理
* date: 2022/03/21 14:45
* author: LenonJin
*
* @param str
* @return java.lang.String
*/
String sensitive(String str) {
if (StringUtils.isNotBlank(str)) {
// 手机号脱敏
str = str.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
// TODO 按需拓展
}
log.info("Tips :{}", "敏感数据已脱敏.");
return str;
}
}
注解使用
测试demo
package com.example.practise.controller;
import com.example.practise.annotation.WebLog;
import org.springframework.web.bind.annotation.*;
/**
* description: 测试日志打印
* date: 2022/03/21 15:42
* author: LenonJin
*/
@RestController
@RequestMapping("/log")
public class LogController {
/**
* test @RequestParam
*/
@WebLog(description = "测试接口一")
@GetMapping("/getBookNameById")
public String getBookNameById(@RequestParam("id") String id) {
return "平凡的世界";
}
/**
* test @PathVariable
*/
@WebLog(description = "测试接口二")
@GetMapping("/getBookById/{id}")
public BookRes getBookByTitle(@PathVariable("id") String id) {
BookRes bookRes = new BookRes();
bookRes.setAuthor("LenonJin");
bookRes.setName("JAVA从入门到放弃");
bookRes.setDesc("隐形贫困人口:指某些人看起来有吃有喝有玩,但实际上非常穷。");
return bookRes;
}
/**
* test post
*/
@WebLog(description = "测试接口三")
@PostMapping("/getBook")
public BookRes getBook(@RequestBody BookReq request) {
BookRes bookRes = new BookRes();
bookRes.setAuthor("毛姆");
bookRes.setName("月亮与六便士");
bookRes.setDesc("理想(月亮)和现实(六便士)如何选择?这是摆在每个人面前的选择。");
return bookRes;
}
/**
* test printReq use
*/
@WebLog(description = "测试关闭请求参数打印", printReq = false)
@GetMapping("/testOne")
public String testOne(@RequestParam("password") String password) {
return "SUCCESS";
}
/**
* test sensitiveReq use
*/
@WebLog(description = "测试请求参数脱敏", sensitiveReq = true)
@GetMapping("/testTwo")
public String testTwo(@RequestParam("mobile") String mobile) {
return "SUCCESS";
}
}
打印效果
2022-03-21 16:16:59.905 INFO 3776 --- [nio-8080-exec-5] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-03-21 16:16:59.905 INFO 3776 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-03-21 16:16:59.911 INFO 3776 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : Completed initialization in 6 ms
2022-03-21 16:16:59.914 INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect : ----------------------------------------------------------- Start -----------------------------------------------------------
2022-03-21 16:16:59.915 INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect : Request Uri :/log/getBookNameById
2022-03-21 16:16:59.915 INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect : Request Ip :127.0.0.1
2022-03-21 16:16:59.916 INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect : Http Method :GET
2022-03-21 16:16:59.916 INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect : Description :测试接口一
2022-03-21 16:16:59.916 INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect : Class Method :com.example.practise.controller.LogController.getBookNameById
2022-03-21 16:16:59.916 INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect : Request Args :{"id":["200"]}
2022-03-21 16:16:59.920 INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect : Response Args :平凡的世界
2022-03-21 16:16:59.920 INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect : Time Consuming :6 ms
2022-03-21 16:16:59.920 INFO 3776 --- [nio-8080-exec-5] c.example.practise.aspectj.WebLogAspect : ------------------------------------------------------------ End ------------------------------------------------------------
2022-03-21 16:17:01.569 INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect : ----------------------------------------------------------- Start -----------------------------------------------------------
2022-03-21 16:17:01.570 INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect : Request Uri :/log/getBookById/200
2022-03-21 16:17:01.570 INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect : Request Ip :127.0.0.1
2022-03-21 16:17:01.570 INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect : Http Method :GET
2022-03-21 16:17:01.570 INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect : Description :测试接口二
2022-03-21 16:17:01.570 INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect : Class Method :com.example.practise.controller.LogController.getBookByTitle
2022-03-21 16:17:01.570 INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect : Request Args :args is null.
2022-03-21 16:17:01.570 INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect : Response Args :{"name":"JAVA从入门到放弃","desc":"隐形贫困人口:指某些人看起来有吃有喝有玩,但实际上非常穷。","author":"LenonJin"}
2022-03-21 16:17:01.570 INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect : Time Consuming :1 ms
2022-03-21 16:17:01.570 INFO 3776 --- [nio-8080-exec-3] c.example.practise.aspectj.WebLogAspect : ------------------------------------------------------------ End ------------------------------------------------------------
2022-03-21 16:17:02.971 INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect : ----------------------------------------------------------- Start -----------------------------------------------------------
2022-03-21 16:17:02.976 INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect : Request Uri :/log/getBook
2022-03-21 16:17:02.976 INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect : Request Ip :127.0.0.1
2022-03-21 16:17:02.976 INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect : Http Method :POST
2022-03-21 16:17:02.977 INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect : Description :测试接口三
2022-03-21 16:17:02.977 INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect : Class Method :com.example.practise.controller.LogController.getBook
2022-03-21 16:17:02.977 INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect : Request Args :[{"name":"月亮与六便士","desc":""}]
2022-03-21 16:17:02.978 INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect : Response Args :{"name":"月亮与六便士","desc":"理想(月亮)和现实(六便士)如何选择?这是摆在每个人面前的选择。","author":"毛姆"}
2022-03-21 16:17:02.978 INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect : Time Consuming :6 ms
2022-03-21 16:17:02.978 INFO 3776 --- [nio-8080-exec-1] c.example.practise.aspectj.WebLogAspect : ------------------------------------------------------------ End ------------------------------------------------------------
2022-03-21 16:17:04.312 INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect : ----------------------------------------------------------- Start -----------------------------------------------------------
2022-03-21 16:17:04.312 INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect : Request Uri :/log/testOne
2022-03-21 16:17:04.312 INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect : Request Ip :127.0.0.1
2022-03-21 16:17:04.312 INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect : Http Method :GET
2022-03-21 16:17:04.312 INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect : Description :测试关闭请求参数打印
2022-03-21 16:17:04.312 INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect : Class Method :com.example.practise.controller.LogController.testOne
2022-03-21 16:17:04.312 INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect : Request Args :请求参数打印关闭.
2022-03-21 16:17:04.312 INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect : Response Args :SUCCESS
2022-03-21 16:17:04.312 INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect : Time Consuming :0 ms
2022-03-21 16:17:04.312 INFO 3776 --- [nio-8080-exec-7] c.example.practise.aspectj.WebLogAspect : ------------------------------------------------------------ End ------------------------------------------------------------
2022-03-21 16:17:05.918 INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect : ----------------------------------------------------------- Start -----------------------------------------------------------
2022-03-21 16:17:05.919 INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect : Tips :敏感数据已脱敏.
2022-03-21 16:17:05.919 INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect : Request Uri :/log/testTwo
2022-03-21 16:17:05.919 INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect : Request Ip :127.0.0.1
2022-03-21 16:17:05.919 INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect : Http Method :GET
2022-03-21 16:17:05.919 INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect : Description :测试请求参数脱敏
2022-03-21 16:17:05.919 INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect : Class Method :com.example.practise.controller.LogController.testTwo
2022-03-21 16:17:05.919 INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect : Request Args :{"mobile":["176****0007"]}
2022-03-21 16:17:05.919 INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect : Response Args :SUCCESS
2022-03-21 16:17:05.919 INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect : Time Consuming :1 ms
2022-03-21 16:17:05.919 INFO 3776 --- [nio-8080-exec-2] c.example.practise.aspectj.WebLogAspect : ------------------------------------------------------------ End ------------------------------------------------------------
说明
自定义注解:
@Retention(RetentionPolicy.RUNTIME):定义为运行时使用该注解。
@Target({ElementType.METHOD}):定义注解作用于方法上。
切面:
@Pointcut("@annotation(com.example.practise.annotation.WebLog)"):切点,这里的切点切的就是我们的自定义注解WebLog。
@Around("pointCut() && @annotation(logPrint)"): 环绕,可以在切入点前后织入代码,可以自由的控制何时执行切点。
拓展:
切面中(WebLogAspect 143行),脱敏部分的正则表达式可以按照自己的要求添加或修改。