用@Around而非@Before记录操作日志,因其可在方法执行前后介入并捕获返回值与Throwable,支撑成功/失败判定、字段变更追踪及异常原因记录。

为什么用 @Around 而不是 @Before 记录操作日志
因为 @Before 拿不到方法返回值和异常,而真实业务中「操作是否成功」「修改了哪些字段」「失败原因」都得记进日志。用 @Around 可以在方法执行前后都介入,还能捕获 Throwable —— 这是入库时区分「成功/失败」的关键。
常见错误是只记录入参,结果日志里全是 null 或 toString() 的默认输出,比如 User@1a2b3c。必须手动解析参数对象,或用 JSON.toJSONString()(注意循环引用)。
- 别直接打印
joinPoint.getArgs()数组,它只是 Object[],要逐个处理 - 如果参数含敏感字段(如密码、token),必须在序列化前脱敏,不能靠数据库字段掩码
- Spring AOP 默认不代理 final 方法,加了
@Log但没生效?先检查目标方法是不是 final
@Log 注解怎么设计才支持异步入库和字段过滤
注解本身要轻量,只声明行为意图,比如是否记录返回值、是否忽略空参数、指定日志类型(增删改查)。具体逻辑全在切面里做判断,否则注解会越来越重,后期维护成本高。
异步写库不是加个 @Async 就完事——AOP 代理对象在事务外,可能拿到未提交的数据;更麻烦的是,如果方法抛异常回滚了,日志却已落库,数据就不一致。
立即学习“Java免费学习笔记(深入)”;
- 推荐用
TransactionSynchronizationManager.registerSynchronization(),把日志写操作绑定到当前事务生命周期 - 字段过滤靠注解属性控制,比如
@Log(includeReturn = false, excludeFields = {"password", "token"}) - 不要在切面里直接调用
logService.save(),应封装成LogRecord对象,交由专门的记录器处理
获取真实请求参数时,HttpServletRequest 为什么总是 null
因为 AOP 切的是 Service 层方法,而 HttpServletRequest 只在 Web 层(Controller)可用。想记「谁在什么 IP、什么时间、通过哪个接口触发了操作」,得靠 ThreadLocal 透传,或者改用 Spring MVC 的 HandlerInterceptor 配合 AOP 协同。
另一个坑:用 RequestContextHolder 获取 request 时,如果项目用了异步(比如 @Async 或线程池),默认上下文不传递,getRequestAttributes() 直接返回 null。
- WebMvcConfigurer 中配置
ThreadPoolTaskExecutor时,必须设置setTaskDecorator(new RequestContextTaskDecorator()) - 不要在切面里反复调用
RequestContextHolder.currentRequestAttributes(),先判空再取,否则 NPE - 如果用了 Dubbo 或 RPC,HTTP 上下文彻底不可用,得靠 RPC 的 attachment 机制传 traceId 和用户 ID
日志入库性能差、MySQL 写满 CPU,问题大概率出在这三个地方
不是日志量大就该上 Elasticsearch,很多项目卡在基础设计上:比如每次记录都新建 JSONObject、没复用 PreparedStatement、SQL 没走索引导致慢查询拖垮整个库。
还有种隐蔽问题:切面没加条件拦截,连健康检查接口 /actuator/health 都被记日志,QPS 上千后日志表暴涨。
- 加
if (!method.getName().startsWith("get") && !method.getName().matches("is[A-Z].*"))过滤 getter 方法 - 用
StringBuilder拼接 SQL,避免字符串 + 导致频繁 GC;批量写用JdbcTemplate.batchUpdate() - 日志表必须有
create_time索引,按天分表或加归档任务,别让单表超 500 万行
最常被忽略的是:切面里没做 try-catch 包裹日志逻辑。一旦数据库挂了或 JSON 序列化崩了,整个业务方法跟着失败——日志功能反而成了故障放大器。










