Spring Boot中通过AOP实现接口访问日志切面,需定义@Aspect类精准拦截Controller方法,记录路径、参数(脱敏)、响应、耗时及异常,并优化日志范围、性能与上下文信息获取。

在 Spring Boot 中通过 AOP 实现接口访问日志切面,核心是定义一个切面类,拦截 Controller 层方法执行前后,记录请求路径、参数、响应结果、耗时、异常等关键信息。重点在于精准拦截、避免敏感数据泄露、控制日志粒度,并适配 RESTful 风格。
1. 添加依赖和启用 AOP
确保项目已引入 spring-boot-starter-aop(Spring Boot 2.6+ 默认包含,无需额外添加),并在启动类或配置类上确认启用了 AOP:
- Spring Boot 2.4+ 默认开启 @EnableAspectJAutoProxy,一般无需显式添加
- 如需代理 CGLIB(比如目标类无接口),可加:
@EnableAspectJAutoProxy(proxyTargetClass = true)
2. 定义日志切面类
创建一个带 @Aspect 和 @Component 的类,使用 @Around 拦截 Controller 方法(推荐),也可用 @Before+@AfterReturning+@AfterThrowing 组合实现:
@Aspect
@Component
@Slf4j
public class AccessLogAspect {
<pre class="brush:php;toolbar:false;">@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping) || " +
"@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PostMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PutMapping) || " +
"@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
public Object logAccess(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
String methodName = joinPoint.getSignature().toShortString();
Object[] args = joinPoint.getArgs();
// 记录请求开始(脱敏处理参数)
log.info("→ {} | args: {}", methodName, maskSensitiveArgs(args));
Object result;
try {
result = joinPoint.proceed();
long cost = System.currentTimeMillis() - start;
log.info("← {} | result: {} | cost: {}ms", methodName, getSimpleResult(result), cost);
return result;
} catch (Exception e) {
long cost = System.currentTimeMillis() - start;
log.error("✗ {} | exception: {} | cost: {}ms", methodName, e.getClass().getSimpleName(), cost, e);
throw e;
}
}
private String maskSensitiveArgs(Object[] args) {
return Arrays.stream(args)
.map(arg -> arg instanceof String && ((String) arg).length() > 20
? ((String) arg).substring(0, 10) + "..." + ((String) arg).substring(((String) arg).length() - 5)
: Objects.toString(arg, "null"))
.collect(Collectors.joining(", ", "[", "]"));
}
private String getSimpleResult(Object result) {
if (result == null) return "null";
if (result instanceof String || result instanceof Number || result instanceof Boolean) {
return String.valueOf(result);
}
return result.getClass().getSimpleName() + "@" + Integer.toHexString(result.hashCode());
}}
3. 控制日志范围与性能优化
避免全包扫描或过度拦截,提升稳定性和可观测性:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 用更精确的切入点表达式,例如:
@Within(org.example.controller..*)限定只切 controller 包下类 - 对文件上传、大 JSON 响应、二进制流等接口,建议跳过详细参数/结果日志,仅记录路径和状态
- 生产环境建议将日志级别设为
INFO或DEBUG,并通过application.yml动态开关 - 考虑异步写入日志(如 Logback 的 AsyncAppender),避免阻塞主业务线程
4. 补充:结合 RequestContextHolder 获取请求上下文
若需记录 IP、User-Agent、TraceId 等,可在切面中获取当前 HTTP 请求:
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = attrs.getRequest();
String ip = getClientIp(request);
String userAgent = request.getHeader("User-Agent");
String traceId = request.getHeader("X-B3-Traceid"); // 若集成 Sleuth
注意:RequestContextHolder 默认绑定到当前线程,异步方法中需手动传递或使用 RequestScope。
不复杂但容易忽略的是日志脱敏和性能边界——别让日志成为瓶颈,也别把密码、身份证号打出来。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










