Spring框架的两大核心技术——IoC(控制反转)和AOP(面向切面编程),共同构建了现代Java企业级应用开发的基石。很多开发者在实际项目中天天用@Transactional和日志切面,却说不清AOP的底层实现原理,面试时一问就卡壳。本文将带你深入剖析Spring AOP的核心概念、动态代理机制与底层原理,配合代码示例和面试真题,建立完整的知识链路。
一、痛点切入:传统OOP为什么搞不定“横切关注点”?

在传统的面向对象编程中,模块化的基本单位是类(Class)。一个典型的业务类如UserService,其核心职责是处理用户相关的业务逻辑。在实际开发中,每个方法都不可避免地要承担额外职责:
public class UserService {public void createUser(User user) { // 日志记录 System.out.println("开始执行createUser方法,参数:" + user); // 权限校验 if (!hasPermission("ADMIN")) throw new RuntimeException("无权限"); // 核心业务逻辑 userDao.insert(user); // 事务管理 try { // ... 业务操作 transaction.commit(); } catch (Exception e) { transaction.rollback(); } } }
这种编码方式暴露了传统OOP在处理横切关注点时的两大结构性难题-50:
代码冗余:同样的日志、安全、事务逻辑被复制到
UserService、OrderService、ProductService的每一个方法中耦合度高:核心业务逻辑与非业务逻辑纠缠在一起,违反了单一职责原则,导致代码难以阅读、测试和维护
AOP(Aspect-Oriented Programming)正是为解决这一结构性难题而诞生。它将横切关注点从业务逻辑中剥离出来,形成独立的模块(切面),通过动态代理在运行时织入目标代码中--47。
二、核心概念:AOP的本质与术语体系
什么是AOP?
AOP(Aspect-Oriented Programming,面向切面编程) 是一种编程范式,它允许开发者在不修改业务代码的前提下,为方法统一添加横切逻辑(如日志、事务、权限),通过动态代理在方法执行前后织入增强逻辑-。
一句话理解AOP:像CSS选择器批量定义样式一样,用“切点表达式”批量拦截方法并执行统一操作。
核心术语拆解
| 术语 | 英文 | 一句话理解 |
|---|---|---|
| 切面 | Aspect | 横切关注点的模块化实现(类+切点+通知) |
| 连接点 | Join Point | 程序执行过程中可被拦截的点(Spring仅支持方法执行) |
| 切点 | Pointcut | 匹配连接点的表达式,定义“在哪里增强” |
| 通知 | Advice | 切面在连接点上执行的操作,定义“做什么”和“什么时候做” |
| 织入 | Weaving | 将切面应用到目标对象、创建代理对象的过程 |
AOP在OOP中模块化的单元是“类”,而在AOP中模块化的新单元是“切面(Aspect)”——这个类比可以帮助快速理解:AOP负责定义批量横切逻辑,OOP负责定义单个业务模块-54。
生活化类比:想象一家餐厅,厨房里切菜、炒菜是核心业务;而每个菜品都要经历的“做之前检查食材是否过期”和“做完后拍照上传”就是横切逻辑。传统方式:每个菜品单独写检查食材和拍照的代码(冗余)。AOP方式:定义好规则——所有菜品(切点)在上菜前都要检查食材(前置通知),上菜后拍照(后置通知)。这个规则本身就是一个切面。
三、关联概念:Spring AOP的五种通知类型
Spring AOP提供了五种通知类型,覆盖方法执行的全生命周期:
| 通知类型 | 注解 | 执行时机 |
|---|---|---|
| 前置通知 | @Before | 目标方法执行前 |
| 后置通知 | @After | 目标方法执行后(无论是否有异常) |
| 返回后通知 | @AfterReturning | 目标方法正常返回后 |
| 异常后通知 | @AfterThrowing | 目标方法抛出异常后 |
| 环绕通知 | @Around | 完全控制目标方法的执行(最强) |
@Around是最强大的通知类型,因为它能通过ProceedingJoinPoint完全控制方法执行链,可以决定是否执行原方法、修改返回值、捕获异常等-21-3。
四、概念关系与区别总结
AOP概念体系的内在逻辑关系可以这样梳理:
切点(Pointcut) 负责定位——用表达式告诉Spring“哪些方法需要被增强”
通知(Advice) 负责实现——编写增强逻辑本身
切面(Aspect) 将切点和通知封装在一起,形成一个可重用的模块
织入(Weaving) 是将切面应用到目标对象、生成代理对象的过程
一句话概括:切点告诉Spring“在哪干”,通知告诉Spring“怎么干”,切面把它们打包好,织入是Spring帮你干的过程。
OOP vs AOP:OOP通过垂直继承实现代码复用,AOP通过水平抽取实现关注点分离,二者互为补充,而非替代关系-。
五、代码示例:从零实现一个日志切面
步骤1:添加依赖(Spring Boot项目)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
步骤2:定义切面类
@Aspect // 标记为切面类 @Component // 纳入Spring容器管理 @Slf4j public class LoggingAspect { // 定义切点:匹配com.example.service包下所有类的所有方法 @Pointcut("execution( com.example.service..(..))") public void servicePointcut() {} // 前置通知:方法执行前记录日志 @Before("servicePointcut()") public void logBefore(JoinPoint joinPoint) { log.info("执行方法:{},参数:{}", joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs())); } // 环绕通知:计算方法执行耗时 @Around("servicePointcut()") public Object measureTime(ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); Object result = pjp.proceed(); // 执行目标方法 long elapsed = System.currentTimeMillis() - start; log.info("方法 {} 执行耗时:{}ms", pjp.getSignature().getName(), elapsed); return result; } }
执行流程说明
Spring容器在启动时扫描所有Bean,发现带有@Aspect的切面类后,会根据切点表达式匹配目标Bean,为匹配的Bean动态生成代理对象,并将代理对象注入容器-1。当业务代码调用目标方法时,实际调用的是代理对象——它先执行切面逻辑(如日志),再调用原始目标方法。
六、底层原理:Spring AOP的动态代理机制
两种动态代理方式
Spring AOP底层依赖动态代理实现横切逻辑与业务逻辑的解耦,主要有两种技术路线-11:
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 实现原理 | 基于接口,通过反射生成代理类 | 基于继承,通过ASM字节码生成子类 |
| 代理前提 | 目标类必须实现接口 | 无接口即可,但类不能是final |
| 代理方式 | 生成接口的实现类 | 生成目标类的子类 |
| 可代理方法 | 仅接口的public方法 | 非final的public/protected/包私有方法 |
| final限制 | 无影响 | 无法代理final类/方法 |
| 依赖 | Java标准库 | 需要CGLIB库(Spring Boot已内置) |
Spring的代理选择策略
Spring通过DefaultAopProxyFactory自动判断:若目标类实现了接口且未强制使用CGLIB,则优先使用JDK动态代理;否则使用CGLIB-11。
在Spring Boot 3.x中,默认已启用proxyTargetClass=true,因此更多时候使用CGLIB代理-26。
100个对象如何统一代理?
Spring AOP的“动态”体现在运行时动态生成代理类,而非编译期手动编写。无论有多少个目标对象,只需一套横切逻辑定义,Spring容器在初始化时根据切点表达式自动为所有匹配的Bean生成代理,无需手动编写代理代码-1。
七、高频面试题与参考答案
面试题1:什么是AOP?Spring AOP是如何实现的?
参考答案:AOP(面向切面编程)是在不修改业务代码的前提下,为方法统一添加横切逻辑(如日志、事务、权限)的机制,通过动态代理在方法执行前后织入增强-3。
Spring AOP基于动态代理实现:如果目标类实现了接口,使用JDK动态代理;如果没有接口,使用CGLIB生成子类代理。Spring容器最终注入的是代理对象而非原始对象-3。
面试题2:JDK动态代理和CGLIB有什么区别?Spring如何选择?
参考答案:JDK动态代理基于接口,通过Proxy.newProxyInstance()生成代理类,要求目标类必须实现接口;CGLIB通过ASM字节码技术生成目标类的子类作为代理,无需接口支持,但无法代理final类/方法-4-11。Spring根据目标类是否实现接口自动选择,同时可通过proxyTargetClass=true强制使用CGLIB。
面试题3:为什么@Transactional有时会失效?
参考答案:常见原因包括:
方法不是
public(事务只作用于public方法)在同一个类内部调用(没有经过代理对象)
final方法无法被代理类标注
@Transactional但方法没有public
核心要点:内部调用没有经过代理对象,AOP不生效-3。
面试题4:Spring AOP和AspectJ有什么区别?
参考答案:Spring AOP是运行时动态代理实现,功能有限但足以满足日常业务需求;AspectJ是编译时织入,功能更强大。面试标准回答:Spring AOP是基于代理的运行时AOP框架,而AspectJ是基于字节码织入的编译时AOP框架-3。
八、结尾总结
核心知识点回顾
AOP解决什么问题:传统OOP在处理横切关注点时存在代码冗余、耦合度高的问题
核心概念:切面、连接点、切点、通知、织入——五个术语构成了AOP的完整体系
底层实现:JDK动态代理(基于接口)和CGLIB(基于继承)两种方式
使用方式:通过
@Aspect+@Pointcut+ 通知注解,声明式定义切面
易错点提醒
混淆JDK和CGLIB的适用场景:JDK必须要有接口,CGLIB不能代理final类/方法
忘记加
@Component导致切面类未被Spring管理内部调用导致代理失效
切点表达式写错导致无匹配方法
进阶预告
下一篇将深入讲解AOP的执行链路——从方法调用到拦截器链的执行过程,以及@EnableAspectJAutoProxy背后的源码实现。

扫一扫微信交流