当前位置: 首页 > news >正文

【硬核实战】Spring AOP 从原理到落地:3 个可运行案例带你吃透切面编程

作者风格:MY_TEUCK 硬核实战风 + AI 应用落地风
读者对象:Java 学习者 / Spring Boot 初中级开发者
阅读目标:不仅知道 AOP 是什么,更知道什么时候用、怎么写、怎么避坑


前言

很多同学第一次学 AOP(面向切面编程)会有两个误区:

  1. 只会背概念:Aspect、Pointcut、Advice,但写不出可用代码。
  2. 只会抄例子:日志打印能跑,但一到业务场景(权限、幂等、审计)就不会落地。

这篇文章直接走实战路线:
先把思想讲透,再给你可运行代码,最后落到真实业务场景和面试答法,确保你学完就能用。


一、概念解析:AOP 到底是什么?

1.1 一句话定义

AOP 是把“横切关注点”从业务代码中抽离出来,统一管理的一种编程思想。

什么叫横切关注点?

  • 不是核心业务本身(比如下单、支付、查询订单)
  • 但很多地方都要做(比如日志、权限、事务、审计、幂等)

1.2 为什么需要 AOP?

如果不用 AOP,代码常变成这样:

  • 每个方法都写一遍日志
  • 每个接口都写一遍权限判断
  • 每次更新都手动补审计字段

结果是:重复、易漏、难维护。

1.3 AOP 与 OOP 的关系

  • OOP:负责纵向建模(订单服务、商品服务、用户服务)
  • AOP:负责横向增强(日志、权限、监控、审计)

它们不是替代关系,而是互补关系。


二、原理:Spring AOP 是怎么工作的?

Spring AOP 的核心不是“魔法”,而是代理模式

2.1 执行链路

  1. Spring 启动扫描到切面(@Aspect
  2. 根据切点(Pointcut)匹配目标方法
  3. 为目标 Bean 创建代理对象(JDK 动态代理 / CGLIB)
  4. 你调用的是代理对象
  5. 代理在合适时机执行通知(Advice)再执行目标方法

2.2 关键术语(必须会)

  • Aspect(切面):承载增强逻辑的类
  • Pointcut(切点):定义拦截哪些方法
  • Advice(通知):定义何时增强(前置/后置/环绕/异常)
  • JoinPoint(连接点):可被增强的方法执行点
  • Weaving(织入):把切面应用到目标对象的过程

三、实战代码:3 个可运行 AOP 案例

下面示例基于 Spring Boot,直接可用。

3.1 准备依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

3.2 案例一:接口耗时统计(最直观)

(1)切面代码
packagecom.example.aop;importlombok.extern.slf4j.Slf4j;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.springframework.stereotype.Component;@Aspect@Component@Slf4jpublicclassCostTimeAspect{@Around("execution(* com.example.service..*(..))")publicObjectrecordTime(ProceedingJoinPointpjp)throwsThrowable{longstart=System.currentTimeMillis();try{returnpjp.proceed();}finally{longcost=System.currentTimeMillis()-start;log.info("method={} cost={}ms",pjp.getSignature().toShortString(),cost);}}}
(2)业务代码
packagecom.example.service;importorg.springframework.stereotype.Service;@ServicepublicclassOrderService{publicStringcreateOrder()throwsInterruptedException{Thread.sleep(120);return"ok";}}

3.3 案例二:自定义注解 + 权限校验

(1)定义注解
packagecom.example.aop;importjava.lang.annotation.*;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceRequiresRole{Stringvalue();}
(2)切面校验
packagecom.example.aop;importorg.aspectj.lang.JoinPoint;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.annotation.Before;importorg.aspectj.lang.reflect.MethodSignature;importorg.springframework.stereotype.Component;@Aspect@ComponentpublicclassRoleCheckAspect{@Before("@annotation(com.example.aop.RequiresRole)")publicvoidcheckRole(JoinPointjoinPoint){MethodSignaturesignature=(MethodSignature)joinPoint.getSignature();RequiresRolerequiresRole=signature.getMethod().getAnnotation(RequiresRole.class);StringneedRole=requiresRole.value();// 示例:真实项目可从 ThreadLocal / JWT 中取当前用户角色StringcurrentRole="USER";if(!needRole.equals(currentRole)){thrownewRuntimeException("无权限访问,needRole="+needRole);}}}
(3)使用注解
packagecom.example.service;importcom.example.aop.RequiresRole;importorg.springframework.stereotype.Service;@ServicepublicclassAdminService{@RequiresRole("ADMIN")publicStringdeleteUser(){return"deleted";}}

3.4 案例三:公共字段自动填充(企业项目高频)

这个场景是很多管理系统都存在的真实需求:

  • 新增时自动填充createTime/createUser/updateTime/updateUser
  • 更新时自动填充updateTime/updateUser
(1)注解 + 操作类型
publicenumOperationType{INSERT,UPDATE}
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceAutoFill{OperationTypevalue();}
(2)切面核心逻辑
@Aspect@ComponentpublicclassAutoFillAspect{@Before("execution(* com.example.mapper..*(..)) && @annotation(com.example.aop.AutoFill)")publicvoidautoFill(JoinPointjoinPoint)throwsException{MethodSignaturesignature=(MethodSignature)joinPoint.getSignature();AutoFillautoFill=signature.getMethod().getAnnotation(AutoFill.class);OperationTypetype=autoFill.value();Objectentity=joinPoint.getArgs()[0];LocalDateTimenow=LocalDateTime.now();LongcurrentUserId=1L;// 示例,真实项目从上下文取if(type==OperationType.INSERT){entity.getClass().getMethod("setCreateTime",LocalDateTime.class).invoke(entity,now);entity.getClass().getMethod("setCreateUser",Long.class).invoke(entity,currentUserId);}entity.getClass().getMethod("setUpdateTime",LocalDateTime.class).invoke(entity,now);entity.getClass().getMethod("setUpdateUser",Long.class).invoke(entity,currentUserId);}}

四、场景案例:AOP 在业务中怎么选?

4.1 订单系统

  • 下单接口:幂等切面,防重复提交
  • 核心服务:耗时切面,定位慢调用
  • 管理接口:权限切面,拦截越权操作

4.2 后台管理系统

  • 数据变更:操作审计切面(谁、何时、改了什么)
  • 增删改接口:统一日志切面
  • 通用字段:自动填充切面

4.3 AI 应用落地场景

在 AI 业务里,AOP 同样有价值:

  • 模型调用耗时统计(推理耗时、token 消耗)
  • Prompt 安全校验(敏感词、越权指令)
  • 统一打点埋点(命中率、错误率、重试次数)

重点:AOP 不是传统业务专属,在 AI 工程化里同样是“横向治理利器”。


五、避坑指南(实战高频)

5.1 自调用失效

同类中this.xxx()调用可能绕过代理,导致切面不生效。
解决:通过代理对象调用,或拆分到其他 Bean。

5.2 切点写错

  • 写太宽:误伤无关方法
  • 写太窄:该拦截的没拦截

建议先在切面里打日志确认命中范围。

5.3 多切面顺序混乱

多个切面叠加时用@Order明确顺序,避免执行链不可控。

5.4 环绕通知漏写proceed()

@Around不调用proceed(),目标方法不会执行。这个坑非常常见。

5.5 反射异常未处理

自动填充常用反射,setter 名称、参数类型要严格一致,异常处理要完整。


六、面试题(高频 + 可直接回答)

题 1:AOP 的核心价值是什么?

把横切关注点(日志、权限、事务等)从业务逻辑中剥离,减少重复代码,提高一致性和可维护性。

题 2:Spring AOP 和动态代理有什么关系?

Spring AOP 基于代理模式实现增强:目标对象被代理后,在方法执行前后插入通知逻辑。

题 3:@Before@Around怎么选?

  • 只做前置校验:@Before
  • 需要统计耗时、控制是否执行原方法:@Around

题 4:为什么事务有时不生效?

常见原因是自调用绕过代理、方法不是public、异常被吞掉或配置不当。

题 5:AOP 适合所有逻辑吗?

不适合。只建议用于跨模块、规则统一、与业务相对解耦的逻辑。


总结

AOP 真正的价值不是“写了个切面”,而是建立一套工程化思维:

  • 把重复横向逻辑统一治理
  • 让业务代码专注核心业务
  • 让系统更稳定、更可维护、更易扩展

你可以从这条落地路径开始:

  1. 先做耗时统计切面(最快看到收益)
  2. 再做权限切面(业务价值高)
  3. 最后做审计/幂等/自动填充(系统化治理)

当你把 AOP 用到“看不到它,但处处受益”的程度,你就真正掌握它了。

http://www.jsqmd.com/news/679720/

相关文章:

  • 良品铺子年营收55亿:同比降23% 净亏1.5亿 拟派息1亿 控股股东3500万债务违约
  • 别再只会用定向天线了!聊聊农村、郊区基站背后的‘全向高增益’技术(附5种主流结构对比)
  • STM32F407ZGT6高级定时器驱动二自由度舵机云台:从PWM原理到安装校准全解析
  • 别再为Instant-NGP发愁!Win11下用Anaconda搞定tiny-cuda-nn环境(附VS2019编译避坑指南)
  • “太空智算互联网”专家观点分享
  • 别再手动改代码格式了!用IntelliJ IDEA的CheckStyle插件,5分钟搞定团队代码规范
  • 从CPU到硬盘:数据的一生之旅,揭秘RAM、Cache、ROM如何接力跑
  • python packer
  • 从光编到绝编:为什么你的伺服项目该考虑SSI/BISS编码器了?
  • 手把手教你用Verilog驱动JFM25F32A Flash:从状态机设计到时序参数避坑
  • LinkSwift:八大网盘直链下载助手,告别下载限速的终极解决方案
  • 别再死记硬背了!用这5个真实场景,彻底搞懂Promise.all、race、any、allSettled的区别
  • 如何在 Gin 框架中自定义 JSON 响应的 Content-Type 头部
  • 【Docker 27存储驱动性能跃迁指南】:27项内核级调优技巧,实测I/O吞吐提升3.8倍
  • 别再傻傻重装软件了!Win7/Win10报错‘丢失api-ms-win-crt-runtime-l1-1-0.dll’的终极修复指南
  • WarcraftHelper:魔兽争霸III的终极现代兼容方案
  • 华为交换机STP配置的5个实战优化技巧:从根保护到BPDU防护,让你的网络更稳
  • 别再死记硬背!用这10道经典算法题,彻底搞懂时间/空间复杂度(附408真题解析)
  • AndroidPdfViewer打印功能完整指南:3步实现PDF文档打印
  • Java项目Loom化实战:3步完成Spring WebFlux与虚拟线程深度整合(含生产级架构图)
  • 2026年打包式箱房怎么选:集装箱特色民宿、高端定制集装箱房、商铺集装箱房、定制化集装箱房、工地住人集装箱、带装修集装箱房选择指南 - 优质品牌商家
  • 2026英文降AIGC率实操:别再盲目同义词替换了!5种降AI高效方法实测(附工具测评)
  • 别再被-c pytorch坑了!手把手教你用Conda搞定PyTorch+PyG环境(附国内源配置)
  • 别再死记硬背网络结构了!用CSPNet思想轻松优化你的ResNet/DenseNet模型
  • OpenCV imread踩坑记:为什么你的透明背景图片在QT里变黑了?
  • 别只盯着高速信号!深入MIPI DSI的‘后台’:Escape Mode与LPDT协议详解(附状态转换图)
  • 深入浅出:从ST-LINK到CMSIS-DAP,一文搞懂ARM调试器的工作原理与DIY精髓
  • STC15W104单片机8脚4路2262 1527解码输出程序-带学习功能与掉电储存功能
  • 别再瞎写了!一份真正能用的SRS模板(含需求可追踪性实战)
  • python vagrant