Spring Boot 用 AOP 做方法级日志与耗时统计:@Around 切面实战
Spring Boot 用 AOP 做方法级日志与耗时统计:Around 切面实战排查线上慢接口时,最想知道的是「到底哪个方法慢」。最土的办法是在每个 Service 方法开头long start System.currentTimeMillis(),结尾算差值打日志。写三五个方法还行,几十个方法全手动埋点,代码里到处是和业务无关的计时噪声,还容易漏掉某个 return 分支导致日志不打。Spring AOP 就是干这个的:把「记日志、算耗时」这种横切关注点从业务代码里剥离,用一个切面统一织入。这篇手把手写一个Around切面,自动给指定方法打入参、出参、耗时和异常日志,顺带讲清几个新手必踩的坑。朴素写法:计时代码淹没业务先看手动埋点长什么样:publicOrdercreateOrder(LonguserId,BigDecimalamount){longstartSystem.currentTimeMillis();log.info(createOrder 入参 userId{}, amount{},userId,amount);try{OrderorderdoCreate(userId,amount);// 真正的业务就这一行log.info(createOrder 耗时 {}ms,System.currentTimeMillis()-start);returnorder;}catch(Exceptione){log.error(createOrder 异常,e);throwe;}}真正的业务只有doCreate一行,其余全是计时和日志。每个方法都这么写,重复且易错——比如某个 early return 分支忘了打耗时日志,排查时就断线了。第一步:引依赖,开一个自定义注解先加 AOP 依赖(Spring Boot 里一个 starter 搞定):dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependency我们不想给「所有方法」都织日志,而是想精确控制哪些方法要统计。用一个自定义注解当「标记」最清晰:importjava.lang.annotation.*;Target(ElementType.METHOD)// 只能标在方法上Retention(RetentionPolicy.RUNTIME)// 运行时可反射读到,AOP 才能识别publicinterfaceLogExecution{Stringvalue()default;// 可选:给这次统计起个业务名}Retention(RUNTIME)是关键——注解信息必须保留到运行时,切面才读得到。写成默认的CLASS级别,运行期就拿不到注解,切面永远不触发。第二步:写一个 Around 切面Around是环绕通知,能力最全:方法执行前后都能插代码,还能拿到入参、控制是否放行、捕获返回值和异常。importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.reflect.MethodSignature;importorg.slf4j.*;importorg.springframework.stereotype.Component;importjava.util.Arrays;AspectComponent// 必须交给 Spring 管理,切面才生效publicclassLogExecutionAspect{privatestaticfinalLoggerlogLoggerFactory.getLogger(LogExecutionAspect.class);// 切点:所有标了 LogExecution 的方法Around(annotation(logExecution))publicObjectaround(ProceedingJoinPointpjp,LogExecutionlogExecution)throwsThrowable{MethodSignaturesig(MethodSignature)pjp.getSignature();StringnamelogExecution.value().isEmpty()?sig.getMethod().getName()// 没起名就用方法名:logExecution.value();log.info([{}] 入参: {},name,Arrays.toString(pjp.getArgs()));longstartSystem.currentTimeMillis();try{Objectresultpjp.proceed();// 放行,执行真正的业务方法longcostSystem.currentTimeMillis()-start;log.info([{}] 返回: {}, 耗时: {}ms,name,result,cost);returnresult;// 必须把结果原样返回}catch(Throwablee){longcostSystem.currentTimeMillis()-start;log.error([{}] 异常: {}, 耗时: {}ms,name,e.getMessage(),cost);throwe;// 必须重新抛出,别把异常吞了}}}两个「必须」划重点:pjp.proceed()的返回值必须原样 return。忘了 return,业务方法的结果就被切面吞掉,调用方永远拿到null。catch 里必须throw e重新抛。切面只是旁观记录,不该改变业务的异常语义。吞掉异常会让上层以为一切正常,是极隐蔽的 bug。第三步:在业务方法上贴注解ServicepublicclassOrderService{LogExecution(创建订单)publicOrdercreateOrder(LonguserId,BigDecimalamount){returndoCreate(userId,amount);// 业务干净了,埋点交给切面}}调用后日志自动产出:[创建订单] 入参: [1001, 99.90] [创建订单] 返回: Order(id8, ...), 耗时: 43ms业务代码回归纯净,计时和日志被切面统一接管。想给哪个方法加统计,贴个注解即可,再不用复制那堆System.currentTimeMillis()。最容易踩的坑:同类内部调用,切面不生效这是 Spring AOP 的头号陷阱。AOP 靠代理对象拦截方法,只有「从外部通过 Spring 注入的 bean 调进来」才会走代理。同一个类里 A 方法直接调用本类的 B 方法(this.B()),走的是原始对象,绕过了代理,切面不触发:ServicepublicclassOrderService{publicvoidbatchCreate(ListReqreqs){for(Reqr:reqs){createOrder(r.userId,r.amount);// this.createOrder,切面不生效!}}LogExecution(创建订单)publicOrdercreateOrder(LonguserId,BigDecimalamount){...}}batchCreate里调createOrder,日志一条都不打——因为是this自调用,没经过代理。解决办法有几种,最简单的是把被切的方法拆到另一个 bean里,通过注入调用:ServicepublicclassOrderService{privatefinalOrderCreatorcreator;// 注入另一个 beanpublicOrderService(OrderCreatorcreator){this.creatorcreator;}publicvoidbatchCreate(ListReqreqs){for(Reqr:reqs){creator.createOrder(r.userId,r.amount);// 走代理,切面生效}}}记住这条规律:AOP 只拦「跨 bean」的调用,同类自调用一律失效。这和 SpringTransactional、Async失效是同一个根因,理解一次受用三处。小结AOP 剥离横切关注点:日志、耗时、鉴权这类和业务无关的代码,用切面统一织入,业务方法回归纯净。自定义注解当开关:注解Retention必须是RUNTIME,配合Around(annotation(...))精确控制切哪些方法。Around两个必须:proceed()的返回值原样 return、catch 里重新throw,否则会吞返回值或吞异常。切面类要Aspect Component:交给 Spring 管理才生效。头号坑:同类自调用失效。AOP 靠代理拦截,this.method()绕过代理;把被切方法拆到另一个 bean、经注入调用即可。这和Transactional/Async自调用失效同源。一句话记忆:AOP 是给方法「套壳」记账的代理,注解标 RUNTIME、around 别吞返回值和异常、自调用记得走别的 bean。

相关新闻

魔兽争霸3优化插件WarcraftHelper:解锁高帧率与完美宽屏体验

魔兽争霸3优化插件WarcraftHelper:解锁高帧率与完美宽屏体验

魔兽争霸3优化插件WarcraftHelper:解锁高帧率与完美宽屏体验 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 魔兽争霸3作为经典的即时战略…

2026/10/3 14:32:08 阅读更多 →
IT项目管理的重要性与实践技巧

IT项目管理的重要性与实践技巧

引言IT项目管理的重要性意味着它不单是应用知识、调整就能和使用常规的工具、技术来帮助项目完成,IT项目经理要应对整个项目的生命周期中可能发生的集成、快速技术更新和版本升级等问题,但是有了熟练的实践技巧,IT项目经理可以自如的应对这些…

2026/10/2 9:11:12 阅读更多 →
终极指南:3分钟快速安装Android Studio中文语言包,告别英文界面困扰

终极指南:3分钟快速安装Android Studio中文语言包,告别英文界面困扰

终极指南:3分钟快速安装Android Studio中文语言包,告别英文界面困扰 【免费下载链接】AndroidStudioChineseLanguagePack AndroidStudio中文插件(官方修改版本) 项目地址: https://gitcode.com/gh_mirrors/an/AndroidStudioChineseLanguage…

2026/9/27 1:24:31 阅读更多 →

最新新闻

OpenClaw接入飞书完整指南:事件订阅、回调配置与消息链路避坑实践

OpenClaw接入飞书完整指南:事件订阅、回调配置与消息链路避坑实践

把 OpenClaw 接到飞书上这件事,我前后折腾了两轮,第一轮卡在飞书事件订阅的 challenge 验证上,第二轮卡在回调超时上。等把整条链路跑通之后回头一看,真正难的不是 OpenClaw 本身,而是飞书开放平台那一堆“看起来简单、…

2026/10/13 8:04:29 阅读更多 →
自动化测试用例编写指南:从筛选到稳定维护

自动化测试用例编写指南:从筛选到稳定维护

做测试这行时间久了,你会遇到一个挺常见的现象:团队刚开始搞自动化的时候,热情都特别高,平台选好了,环境搭好了,测试用例也翻译了一大批。可跑了三两轮,问题就来了——用例时不时闪一下红&#…

2026/10/13 8:04:29 阅读更多 →
深入理解Java自定义注解:从底层机制到敏感字段脱敏处理器实战

深入理解Java自定义注解:从底层机制到敏感字段脱敏处理器实战

自定义注解这个事儿,我最早接触是在某跨平台系统的服务端项目里。当时接口返回给前端的字段,有一部分涉及用户隐私,每次序列化前都要写一大段脱敏逻辑,一个接口里换个字段名就得复制粘贴改一遍,代码又臭又长。后来花了…

2026/10/13 8:04:28 阅读更多 →
如何把 PS2 游戏库压成 CHD:3 条命令指南,省下 60% 磁盘空间

如何把 PS2 游戏库压成 CHD:3 条命令指南,省下 60% 磁盘空间

如何把 PS2 游戏库压成 CHD:3 条命令指南,省下 60% 磁盘空间 【免费下载链接】romm A beautiful, powerful, self-hosted ROM manager and player. 项目地址: https://gitcode.com/GitHub_Trending/rom/romm 4.4GB 的 PS2 ISO,压完只剩…

2026/10/13 8:04:28 阅读更多 →
MiniMax M3上线NVIDIA Vera Rubin:大模型新硬件适配与推理优化实战

MiniMax M3上线NVIDIA Vera Rubin:大模型新硬件适配与推理优化实战

1. 从一次模型部署的“卡顿”说起:为什么硬件适配总在踩坑前阵子帮一个做多模态应用的朋友排查推理延迟的问题,现象很典型:同一套模型权重,在A卡上跑得好好的,换到B卡上吞吐直接掉了一半,显存占用还莫名其妙…

2026/10/13 8:04:28 阅读更多 →
Docker搭建MySQL 8.0主从复制(GTID+ROW模式)实战

Docker搭建MySQL 8.0主从复制(GTID+ROW模式)实战

1. 项目概述:为什么现在还要亲手搭MySQL主从?“Docker搭建基于binlog的MySQL主从同步”——这标题里藏着三个关键信号:容器化、数据一致性、实时复制能力。不是用云数据库一键开启高可用,也不是靠中间件兜底,而是回归底…

2026/10/13 8:03:28 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →