前言:在开发过程中,开发人员难免会遇到打印参数输出的问题,这时候你还在一个一个打印吗?何不尝试封装一下,利用AOP通过管理日志打印。今天我们就通过自定义注解的方式,在SpringBoot中来实现AOP切面统一打印出入参数。有需要的小伙伴可以收藏一波~
一、先看看AOP切面日志输出效果:

从上图可以看到,对于每个请求,实现了对出入参的打印:
- URL: 请求接口地址;
- Description: 接口描述信息;
- HTTP Method: 请求的方法,例如POST、GET、PUT、DELETE等;
- Class Method: 被请求的方法路径,包名 + 方法名;
- IP: 请求方的IP地址;
- Request Args: 请求入参,以JSON格式输出;
- Response Args: 响应出参,以JSON格式输出;
- Time-Consuming: 请求耗时,以此估算每个接口的性能指数;
二、添加AOP Maven依赖
在web项目的pom.xml文件中添加以下依赖:
<!-- aop 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 用于日志切面中,以json格式打印出入参 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
三、自定义日志注解
package com.layduo.common.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义日志注解
*
* @author layduo
* @createTime 2019年11月5日 下午6:33:29
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface WebLog {
/**
* 日志描述信息
*
* @return
*/
String description() default "";
}
-
①:什么时候使用该注解,我们定义为运行时;
-
②:注解用于什么地方,我们定义为作用于方法上;
-
③:注解是否将包含在 JavaDoc 中;
-
④:注解名为
WebLog; -
⑤:定义一个属性,默认为空字符串;
四、配置 AOP 切面
在配置 AOP 切面之前,我们需要了解下 aspectj 相关注解的作用:
-
@Aspect:声明该类为一个注解类;
-
@Pointcut:定义一个切点,后面跟随一个表达式,表达式可以定义为切某个注解,也可以切某个 package 下的方法;
切点定义好后,就是围绕这个切点做文章了:
-
@Before: 在切点之前,织入相关代码;
-
@After: 在切点之后,织入相关代码;
-
@AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;
-
@AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;
-
@Around: 环绕,可以在切入点前后织入代码,并且可以自由的控制何时执行切点;
-
执行顺序:@Around --> @Before --> 接口中逻辑代码 --> @After --> @AfterReturning
package com.layduo.framework.aspectj;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
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 com.google.gson.Gson;
import com.layduo.common.annotation.WebLog;
/**
* @author layduo
* @createTime 2019年11月5日 下午6:36:57
*/
@Aspect
@Component
public class WebLogAspect {
private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
private static final String LINE_SEPARATOR = System.lineSeparator();
/**
* 以自定义注解为切点
*/
@Pointcut("@annotation(com.layduo.common.annotation.WebLog)")
public void webLog(){}
/**
* 环绕
* @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;
}
/**
* 在切点前织入
* @param joinPoint
*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Exception {
//开始打印请求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//获取注解的描述信息
String methodDescription = getAspectLogDescription(joinPoint);
// 打印请求相关参数
logger.info("========================================== Start ==========================================");
// 打印请求 url
logger.info("URL : {}", request.getRequestURL().toString());
// 打印描述信息
logger.info("Description : {}", methodDescription);
// 打印 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()));
}
/**
* 在切点后织入
*/
@After("webLog()")
public void deAfter(){
logger.info("========================================== End ==========================================" + LINE_SEPARATOR);
}
/**
* 获取切面注解的描述
* @param joinPoint 切点
* @return 描述信息
* @throws Exception
*/
public String getAspectLogDescription(JoinPoint joinPoint)
throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class<?> targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
StringBuilder description = new StringBuilder("");
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class<?>[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
description.append(method.getAnnotation(WebLog.class).description());
break;
}
}
}
return description.toString();
}
}
五、如何使用自定义注解,进行日志输出
在controller层,针对某个想输出日志的方法,在其方法上添加自定义日志输出注解@WebLog就行,不需要打印可以不添加:
@RequestMapping("/testWebLog/{name}")
@ResponseBody
@WebLog(description = "测试自定义注解@WebLog")
public String testWebLog(@PathVariable("name") String name) {
return "Welcome to " + name + "!";
}
本文借鉴于:如何优雅地在 Spring Boot 中使用自定义注解,AOP 切面统一打印出入参日志
项目源码已上传github: https://github.com/builthuLin/layduo.git 有需要的自己fork一下~
本文介绍了如何在SpringBoot中利用AOP和自定义注解实现统一的日志打印,包括请求的URL、方法、HTTP方法、类方法、IP、请求和响应参数以及请求耗时。详细步骤包括添加AOP依赖、定义日志注解、配置AOP切面以及在Controller层使用自定义注解。示例代码和项目源码已在GitHub上分享。
1099

被折叠的 条评论
为什么被折叠?



