本文目录导读:

AOP(面向切面编程)记录日志最常用的方式就是利用环绕通知,下面是几种常见场景的写法,从简单到复杂。
基础写法:记录方法入参和返回值
@Aspect
@Component
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
// 定义切面:所有Controller类下的所有方法
@Pointcut("execution(* com.example.controller.*.*(..))")
public void controllerLog() {}
@Around("controllerLog()")
public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
// 方法开始时间
long startTime = System.currentTimeMillis();
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
// 获取参数
Object[] args = joinPoint.getArgs();
log.info("【请求】方法: {},参数: {}", methodName, args);
Object result = null;
try {
result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
log.info("【响应】方法: {},耗时: {}ms,结果: {}", methodName, endTime - startTime, result);
} catch (Exception e) {
log.error("【异常】方法: {},耗时: {}ms,异常: {}", methodName, System.currentTimeMillis() - startTime, e.getMessage());
throw e;
}
return result;
}
}
带自定义注解的写法(推荐)
1 创建日志注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface OperLog {
String value() default ""; // 操作描述
String type() default ""; // 操作类型
}
2 定义切面
@Aspect
@Component
public class OperLogAspect {
private static final Logger log = LoggerFactory.getLogger(OperLogAspect.class);
// 拦截所有带 @OperLog 注解的方法
@Pointcut("@annotation(com.example.annotation.OperLog)")
public void operLogPointcut() {}
@Around("operLogPointcut()")
public Object aroundOperLog(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// 获取注解信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
OperLog operLog = method.getAnnotation(OperLog.class);
// 构建日志信息
String operDescription = operLog.value();
String operType = operLog.type();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
// 获取参数
Object[] args = joinPoint.getArgs();
String params = JSON.toJSONString(args);
// 获取请求信息(Web环境)
HttpServletRequest request = getRequest();
String ip = getIpAddress(request);
String url = request.getRequestURI();
log.info("操作日志 - 描述: {}, 类型: {}, 类名: {}, 方法名: {}, IP: {}, URL: {}, 参数: {}",
operDescription, operType, className, methodName, ip, url, params);
try {
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
log.info("操作日志 - 耗时: {}ms, 结果: {}", endTime - startTime, JSON.toJSONString(result));
return result;
} catch (Exception e) {
log.error("操作日志 - 异常: {}", e.getMessage());
throw e;
}
}
private HttpServletRequest getRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return attributes != null ? attributes.getRequest() : null;
}
private String getIpAddress(HttpServletRequest request) {
if (request == null) return "unknown";
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
3 使用注解
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/add")
@OperLog(value = "添加用户", type = "INSERT")
public Result addUser(@RequestBody User user) {
// 业务逻辑
return Result.success();
}
@PutMapping("/update")
@OperLog(value = "修改用户", type = "UPDATE")
public Result updateUser(@RequestBody User user) {
// 业务逻辑
return Result.success();
}
@DeleteMapping("/delete/{id}")
@OperLog(value = "删除用户", type = "DELETE")
public Result deleteUser(@PathVariable Long id) {
// 业务逻辑
return Result.success();
}
}
异步记录日志(推荐)
@Aspect
@Component
public class AsyncLogAspect {
@Around("@annotation(com.example.annotation.OperLog)")
@Async // 异步执行
public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
// ... 日志记录逻辑
// 注意:异步环境下 RequestContextHolder 可能获取不到,需要手动传递
return joinPoint.proceed();
}
}
完整的最佳实践示例
@Aspect
@Component
@Order(1)
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
// 定义切面
@Pointcut("execution(* com.example.controller.*.*(..))")
public void logPointcut() {}
@Around("logPointcut()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
// 性能监控
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 构建日志信息
LogInfo logInfo = buildLogInfo(joinPoint);
try {
Object result = joinPoint.proceed();
stopWatch.stop();
logInfo.setResult(JSON.toJSONString(result));
logInfo.setCostTime(stopWatch.getTotalTimeMillis());
logInfo.setSuccess(true);
// 记录正常日志
log.info("接口日志: {}", logInfo);
return result;
} catch (Exception e) {
stopWatch.stop();
logInfo.setCostTime(stopWatch.getTotalTimeMillis());
logInfo.setSuccess(false);
logInfo.setErrorMsg(e.getMessage());
// 记录异常日志
log.error("接口异常: {}", logInfo, e);
throw e;
}
}
private LogInfo buildLogInfo(ProceedingJoinPoint joinPoint) {
LogInfo logInfo = new LogInfo();
// 获取方法信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取注解
OperLog operLog = method.getAnnotation(OperLog.class);
if (operLog != null) {
logInfo.setDescription(operLog.value());
logInfo.setType(operLog.type());
}
// 获取请求信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
logInfo.setIp(getIpAddress(request));
logInfo.setUrl(request.getRequestURI());
logInfo.setMethod(request.getMethod());
}
// 获取类和方法名
logInfo.setClassName(signature.getDeclaringTypeName());
logInfo.setMethodName(signature.getName());
// 获取参数
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
// 过滤掉 HttpServletRequest、HttpServletResponse 等
List<Object> filteredArgs = Arrays.stream(args)
.filter(arg -> !(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse))
.collect(Collectors.toList());
logInfo.setParams(JSON.toJSONString(filteredArgs));
}
return logInfo;
}
@Data
public static class LogInfo {
private String description;
private String type;
private String className;
private String methodName;
private String ip;
private String url;
private String httpMethod;
private String params;
private String result;
private long costTime;
private boolean success;
private String errorMsg;
}
}
注意事项
- 性能考虑:生产环境建议异步处理,避免日志记录影响业务性能
- 敏感信息:对密码等敏感信息进行脱敏处理
- 参数过滤:排除 HttpServletRequest、HttpServletResponse 等大对象
- 异常处理:确保日志记录不影响业务正常执行
- 循环依赖:注意切面类不要被自己的切面拦截
这样,你就有了一个完整、可扩展的AOP日志记录方案。