摘要:在实际的开发过程中,我们需要将接口的请求参数、返回数据甚至接口的消耗时间都以日志的形式打印出来以便排查问题,有些比较重要的接口甚至还需要将这些信息写入到数据库。像类似这种场景的代码相对来讲比较相似,为了提高代码的复用率,完全可以以 AOP
的方式将类似的代码封装起来。
相关注解说明:
@Aspect
:将当前类标识为一个切面类,Spring
会将该类作为一个切面管理。@Component
:将该类作为一个Spring
组件。@Order(1)
:主要用来控制配置类的加载顺序,bean
加载的优先级,值越小,越先被加载。@Pointcut
:定义切点表达式,Pointcut
是植入Advice
的触发条件。每个Pointcut
的定义包括2部分,一是表达式,二是方法签名。方法签名必须是public
及void
型。可以将Pointcut
中的方法看作是一个被Advice
引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为此表达式命名。因此Pointcut
中的方法只需要方法签名,而不需要在方法体内编写实际代码。@Before
:通知方法会在目标方法调用之前执行。-
@After
:通知方法会在目标方法返回或抛出异常后执行。 @AfterReturning
:通知方法会在目标方法返回后执行。@AfterThrowing
:通知方法会在目标方法抛出异常后执行。@Around
:环绕增强,在切入点前后切入内容,并自己控制何时执行切入点自身的内容。
切点表达式:指定了通知被应用的范围,格式如下:
execution(方法修饰符 返回类型 方法所属的包.类名.方法名称(方法参数)
@Pointcut("@annotation(com.xxx.annotation.Page)")
@Pointcut("execution(public * com.xxx.controller.*.*(..)) ||
execution(public * com.xxx.*.controller.*.*(..))")
日志切面使用步骤:
- 步骤一:添加
AOP
相关依赖。 -
步骤二:
Controller
层的日志封装类WebLog
。 -
步骤三:统一日志处理切面类
WebLogAspect
。
添加 AOP
相关依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
控制层的日志封装类:
package com.xxx.common.domain;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Controller 层的日志封装类
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class WebLog {
/**
* 操作描述
*/
private String description;
/**
* 操作用户
*/
private String username;
/**
* 操作时间
*/
private Long startTime;
/**
* 消耗时间
*/
private Integer spendTime;
/**
* 根路径
*/
private String basePath;
/**
* URI
*/
private String uri;
/**
* URL
*/
private String url;
/**
* 请求类型
*/
private String method;
/**
* IP地址
*/
private String ip;
/**
* 请求参数
*/
private Object parameter;
/**
* 返回结果
*/
private Object result;
}
统一日志处理切面:
/**
* 统一日志处理切面
*/
@Aspect
@Component
@Order(1)
public class WebLogAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);
@Pointcut("execution(public * com.wangxiong.mall.controller.*.*(..))||execution(public * com.wangxiong.mall.*.controller.*.*(..))")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
String traceLogId = String.valueOf(UUID.randomUUID());
MDC.put("TRACE_LOG_ID", traceLogId);
}
@AfterReturning(value = "webLog()", returning = "ret")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求 清除
MDC.clear();
}
@Around("webLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// 获取当前请求对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录请求信息
WebLog webLog = new WebLog();
// 让目标方法执行
Object result = joinPoint.proceed();
// 获取封装署名信息的对象,在该对象中可以获取到目标方法名,所属类的 Class 等信息
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
// 设置注解描述, method 注释是否在 ApiOperation 上,如果在则返回 true ;不在则返回 false
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation log = method.getAnnotation(ApiOperation.class);
webLog.setDescription(log.value());
}
long endTime = System.currentTimeMillis();
String urlStr = request.getRequestURL().toString();
webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
// 获取当前缓存的用户
webLog.setIp(request.getRemoteUser());
webLog.setMethod(request.getMethod());
// 获取传入目标方法的参数对象
webLog.setParameter(getParameter(method, joinPoint.getArgs()));
webLog.setResult(result);
webLog.setSpendTime((int) (endTime - startTime));
webLog.setStartTime(startTime);
// URI:统一资源标识符 (Uniform Resource Identifier)
webLog.setUri(request.getRequestURI());
// URL: 统一资源定位符 (Uniform Resource Locator)
webLog.setUrl(request.getRequestURL().toString());
Map<String, Object> logMap = new HashMap<>();
logMap.put("url", webLog.getUrl());
logMap.put("method", webLog.getMethod());
logMap.put("parameter", webLog.getParameter());
logMap.put("spendTime", webLog.getSpendTime());
logMap.put("description", webLog.getDescription());
LOGGER.info(Markers.appendEntries(logMap), JSONUtil.parse(webLog).toString());
return result;
}
/**
* 根据方法和传入的参数获取请求参数
*/
private Object getParameter(Method method, Object[] args) {
List<Object> argList = new ArrayList<>();
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
// 将RequestBody注解修饰的参数作为请求参数
RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
if (requestBody != null) {
argList.add(args[i]);
}
// 将RequestParam注解修饰的参数作为请求参数
RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
if (requestParam != null) {
Map<String, Object> map = new HashMap<>();
String key = parameters[i].getName();
if (!StringUtils.isEmpty(requestParam.value())) {
key = requestParam.value();
}
map.put(key, args[i]);
argList.add(map);
}
}
if (argList.size() == 0) {
return null;
} else if (argList.size() == 1) {
return argList.get(0);
} else {
return argList;
}
}
}