2026年开年以来,Spring生态持续活跃——3月13日Spring Framework 6.2.17与7.0.6接连发布,3月下旬Spring Boot 4.1.0的第三个里程碑版本也已亮相-11。无论技术栈如何迭代,Spring AOP始终是开发者绕不开的核心模块。然而很多初学者陷入了“会用但不原理”的困境:知道@Transactional能回滚事务,却说不出失效原因;面试被问“JDK动态代理和CGLIB区别”时语焉不详。本文以Spring AI应用助手串联资料的方式,从代码痛点切入,到概念梳理、底层原理再到面试考点,帮你建立完整的知识链路。
一、痛点切入:为什么需要AOP?

先看一段“反面教材”。假设我们要为UserService的所有方法添加日志记录,传统OOP的做法是在每个方法中手动添加日志代码:
@Servicepublic class UserService { public void saveUser(User user) { System.out.println("【日志】开始保存用户"); // 日志代码侵入业务 // 核心业务逻辑... System.out.println("【日志】用户保存成功"); } public void deleteUser(Long id) { System.out.println("【日志】开始删除用户"); // 日志代码重复 // 核心业务逻辑... System.out.println("【日志】用户删除成功"); } }
这段代码的问题一目了然:代码重复(每个方法都要写日志)、耦合度高(业务逻辑与非功能性代码混杂)、维护困难(修改日志格式要找遍所有方法)。统计数据显示,传统OOP在处理日志、事务等横切关注点时,代码重复率可高达60%以上-20。这正是AOP要解决的问题。
二、核心概念讲解:AOP(Aspect-Oriented Programming)
AOP,全称 Aspect-Oriented Programming(面向切面编程),是一种编程范式,核心思想是在不修改业务源代码的前提下,为程序主干功能动态添加增强逻辑-26。
生活化类比:把业务方法想象成“高速公路主路”,日志、事务、权限校验就像“收费站”“监控摄像头”。传统做法是在主路上直接建收费站,AOP的做法则是单独建一套“辅助设施系统”,动态地“织入”到主路各处,主路代码本身完全不受影响。
AOP的核心术语用一句话串起来:切点(Pointcut) 决定“哪些方法需要被增强”,通知(Advice) 定义“增强什么逻辑”,二者组合成 切面(Aspect) ,在运行时通过 织入(Weaving) 应用到目标对象-21-26。
三、关联概念讲解:AspectJ
AspectJ 是目前Java生态中功能最强大的AOP框架,通过编译时或类加载时织入切面逻辑,支持方法、构造器、字段等更丰富的连接点类型-21。
| 对比维度 | Spring AOP | AspectJ |
|---|---|---|
| 织入时机 | 运行时动态代理 | 编译时/类加载时织入 |
| 连接点范围 | 仅方法级别 | 方法、构造器、字段等 |
| 性能 | 运行时略有开销 | 编译时优化,性能更高 |
| 使用场景 | 日常业务(日志、事务) | 复杂框架级需求 |
一句话总结:Spring AOP ≈ 轻量级运行时AOP(依赖动态代理),AspectJ ≈ 重量级全功能AOP(编译时织入)。大多数业务场景下,Spring AOP已足够;需要更精细控制时才引入AspectJ。
四、底层原理:动态代理与CGLIB
Spring AOP的底层依赖两个核心技术:JDK动态代理和CGLIB。通过DefaultAopProxyFactory智能选择代理策略:若目标类实现了接口且未强制指定CGLIB,使用JDK代理;否则使用CGLIB-26。
JDK动态代理
前置条件:目标类必须实现至少一个接口。底层通过java.lang.reflect.Proxy和InvocationHandler,在运行时动态生成实现目标接口的代理类,所有方法调用经invoke()方法转发,在此处插入增强逻辑-21。
CGLIB代理
适用场景:目标类没有实现接口。CGLIB通过字节码技术动态生成目标类的子类作为代理,在子类中重写父类方法并插入增强逻辑。需注意:final类或final方法无法被CGLIB代理-21。
性能对比
JDK动态代理基于反射机制,而CGLIB生成的代理类是直接调用,通常CGLIB性能更优;但JDK无需引入第三方依赖,实现更轻量-39。
五、代码示例:Spring Boot中实战AOP
Spring Boot为AOP提供了自动配置,只需三步即可上手-50。
第1步:添加依赖(pom.xml)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
第2步:创建切面类
@Aspect @Component public class LogAspect { // 切入点:匹配service包下所有类的所有方法 @Pointcut("execution( com.example.service..(..))") public void serviceMethod() {} @Before("serviceMethod()") public void logBefore(JoinPoint joinPoint) { System.out.println("【前置通知】方法:" + joinPoint.getSignature().getName() + ",参数:" + Arrays.toString(joinPoint.getArgs())); } @AfterReturning(pointcut = "serviceMethod()", returning = "result") public void logAfterReturning(JoinPoint joinPoint, Object result) { System.out.println("【返回通知】返回值:" + result); } @Around("serviceMethod()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); Object result = joinPoint.proceed(); // 执行目标方法 long cost = System.currentTimeMillis() - start; System.out.println("【环绕通知】耗时:" + cost + "ms"); return result; } }
运行效果:每次调用UserService中的方法,日志自动输出,业务代码零侵入。
六、高频面试题与参考答案
Q1:Spring AOP的实现原理是什么?JDK动态代理和CGLIB有何区别?
踩分点:动态代理 + JDK基于接口 + CGLIB基于继承 + Spring代理选择逻辑
AOP通过横向抽取共性功能解决代码重复问题,核心原理是动态代理-39。JDK动态代理基于接口实现,通过反射在运行时生成代理类,要求目标类必须实现接口;CGLIB通过继承目标类生成子类代理,无需接口支持,但不能代理final类/方法。Spring默认优先使用JDK动态代理,目标类无接口时自动切换至CGLIB-39。
Q2:Spring AOP提供了哪些通知类型?
踩分点:五种通知 + 执行时机
@Before(方法执行前)、@After(方法执行后,无论是否异常)、@AfterReturning(方法正常返回后)、@AfterThrowing(方法抛出异常后)、@Around(环绕通知,通过ProceedingJoinPoint.proceed()控制执行流程,功能最强大)-21。
Q3:为什么@Transactional有时会失效?
踩分点:内部调用无代理 + public限制 + final限制
最常见原因:①在同一个类中内部调用方法(未经过代理对象,AOP不生效);②方法不是public的(事务只作用于public方法);③final方法无法被代理-41。
七、结尾总结
回顾全文核心要点:AOP解决的是横切关注点与业务逻辑分离的问题。Spring AOP基于动态代理在运行时织入增强,与编译时织入的AspectJ形成互补;JDK动态代理(需接口)与CGLIB(无接口)是两大底层支撑。面试高频考点集中在动态代理原理、通知类型、@Transactional失效原因三个方向。
易错提醒:切面类必须被Spring容器管理(@Component或@Bean);内部方法调用不走代理,切面不生效。下一篇我们将深入Spring事务管理的底层实现,敬请期待。
