本文首发于2026年4月10日,内容持续同步最新面试考情与技术迭代
一、痛点切入:为什么需要AOP?

传统OOP(Object-Oriented Programming,面向对象编程)在处理日志、事务、权限校验这类横切关注点时,代码会横向散布在所有对象层次中,产生大量重复-1。以日志功能为例:
// 传统方式:每个方法都要手动加日志public void login(String username) { System.out.println("【日志】开始登录,用户:" + username); // 核心登录逻辑 System.out.println("【日志】登录完成"); } public void pay(BigDecimal amount) { System.out.println("【日志】开始支付,金额:" + amount); // 核心支付逻辑 System.out.println("【日志】支付完成"); } public void refund(String orderId) { System.out.println("【日志】开始退款,订单:" + orderId); // 核心退款逻辑 System.out.println("【日志】退款完成"); }
传统方式的三大痛点:
代码冗余:每新增一个方法,都要手动加一遍日志代码,重复率高达60%以上-24。
耦合度高:业务方法与日志、事务代码混在一起,修改日志格式需要改动所有业务类。
维护困难:想给所有方法统一加性能监控,意味着要修改成百上千个方法。
为解决这些痛点,AOP技术应运而生。
二、核心概念讲解:AOP(面向切面编程)
定义: AOP全称Aspect Oriented Programming,面向切面编程,是Spring核心两大思想之一(另一个是IoC)。在不修改原有业务代码的前提下,对方法进行增强,统一处理日志、事务、权限、监控等横切逻辑-8。
生活化类比: 想象你去一家高档餐厅点餐。你只需要告诉服务员“我要一份牛排”,服务员会帮你处理点餐、下单、通知后厨、上菜等整个流程。你不需要亲自跑到后厨炒菜,也不需要自己去洗碗。AOP中的切面就像这个“服务员”——把通用流程抽离出来,让你只关心核心业务。
AOP核心术语一览表:
| 术语 | 含义 | 类比 |
|---|---|---|
| 切面(Aspect) | 封装横切关注点的模块,如日志、事务 | 服务员的工作职责 |
| 连接点(JoinPoint) | 程序执行中可以被拦截的点,Spring中特指方法 | 餐厅里所有可能被服务的环节 |
| 切入点(Pointcut) | 筛选规则,决定哪些连接点被拦截 | “所有点牛排的客人” |
| 通知(Advice) | 拦截后执行的代码逻辑 | 服务员的具体动作 |
五类通知类型及其执行时机:
| 通知类型 | 执行时机 |
|---|---|
| 前置通知(@Before) | 目标方法执行之前执行 |
| 后置通知(@After) | 目标方法执行之后执行(无论是否异常) |
| 返回通知(@AfterReturning) | 目标方法正常返回后执行 |
| 异常通知(@AfterThrowing) | 目标方法抛出异常时执行 |
| 环绕通知(@Around) | 包裹目标方法,功能最强,可控制方法是否执行、修改返回值 |
三、关联概念讲解:代理模式
定义: 代理模式是一种设计模式,通过引入代理对象作为目标对象的中间层,实现对目标对象访问的控制与增强-21。
AOP与代理模式的关系: AOP是一种编程思想,而动态代理是实现这种思想的技术手段。
1. 静态代理:最基础的实现方式
静态代理的代理类在编译期就已确定,与目标类一一对应-41。
// 1. 定义接口(契约) public interface UserService { void addUser(String username); void deleteUser(String username); } // 2. 目标类:真正干活的业务对象 public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("数据库新增用户:" + username); } @Override public void deleteUser(String username) { System.out.println("数据库删除用户:" + username); } } // 3. 静态代理类:手动编写,为目标类附加日志功能 public class UserServiceProxy implements UserService { private final UserService target; // 持有目标对象引用 public UserServiceProxy(UserService target) { this.target = target; } @Override public void addUser(String username) { System.out.println("【日志】开始执行addUser"); target.addUser(username); // 调用真实业务 System.out.println("【日志】addUser执行完成"); } @Override public void deleteUser(String username) { System.out.println("【日志】开始执行deleteUser"); target.deleteUser(username); System.out.println("【日志】deleteUser执行完成"); } }
静态代理的致命缺陷: 如果系统有100个Service类,每个类有5个方法,就需要手动写100个代理类,500个代理方法——维护成本爆炸。
2. 动态代理:运行时生成代理类
动态代理在运行时动态生成代理对象,无需为每个目标类手写代理代码-45。Spring AOP提供了两种动态代理方式:
① JDK动态代理:
要求目标类必须实现至少一个接口,通过java.lang.reflect.Proxy在运行时动态创建代理对象-11。
// JDK动态代理核心代码 public class LogInvocationHandler implements InvocationHandler { private final Object target; // 目标对象 public LogInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("【前置】" + method.getName() + "开始执行"); Object result = method.invoke(target, args); // 反射调用目标方法 System.out.println("【后置】" + method.getName() + "执行完成"); return result; } } // 使用方式 UserService target = new UserServiceImpl(); UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new LogInvocationHandler(target) ); proxy.addUser("张三"); // 调用代理对象,日志自动织入
② CGLIB动态代理:
当目标类没有实现接口时,Spring会使用CGLIB,通过ASM字节码技术动态生成目标类的子类作为代理类-11。
// CGLIB动态代理核心代码 public class LogMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("【前置】" + method.getName() + "开始执行"); Object result = proxy.invokeSuper(obj, args); // 调用父类方法 System.out.println("【后置】" + method.getName() + "执行完成"); return result; } } // 使用方式 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserServiceNoInterface.class); // 目标类(无接口) enhancer.setCallback(new LogMethodInterceptor()); UserServiceNoInterface proxy = (UserServiceNoInterface) enhancer.create(); proxy.addUser("张三");
四、JDK动态代理 vs CGLIB:对比总结
| 对比维度 | JDK动态代理 | CGLIB |
|---|---|---|
| 底层原理 | 基于反射机制,动态生成实现了接口的代理类 | 基于ASM字节码技术,动态生成目标类的子类 |
| 依赖条件 | 目标类必须实现接口 | 不依赖接口,但目标类和方法不能是final |
| 第三方依赖 | Java原生支持,无需额外引入 | 需要cglib库(Spring Core已内置) |
| 代理类创建速度 | 较快 | 较慢(需生成字节码) |
| 方法调用性能 | 通过反射调用,性能略低 | 直接调用,执行效率更高 |
| 局限性 | 无法代理没有接口的类 | 无法代理final类或final方法 |
一句话总结: JDK动态代理是“正规中介公司”——必须有营业执照(接口);CGLIB是“高科技克隆人工厂”——只要有DNA(类结构)就行,不需要执照-12。
五、Spring AOP代码示例
// 1. 定义切面类 @Aspect @Component public class LogAspect { // 2. 定义切入点:拦截com.example.service包下所有类的所有方法 @Pointcut("execution( com.example.service..(..))") public void serviceMethod() {} // 3. 环绕通知:实现日志记录 + 性能监控 @Around("serviceMethod()") public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable { long begin = System.currentTimeMillis(); String methodName = joinPoint.getSignature().getName(); System.out.println("【开始】" + methodName + "执行,参数:" + Arrays.toString(joinPoint.getArgs())); Object result = joinPoint.proceed(); // 调用原始业务方法 long end = System.currentTimeMillis(); System.out.println("【结束】" + methodName + "执行完成,耗时:" + (end - begin) + "ms"); return result; } } // 4. 业务代码:完全不用写日志逻辑 @Service public class OrderService { public void createOrder(OrderDTO order) { // 只关注核心业务,日志自动织入 System.out.println("创建订单:" + order.getOrderId()); } }
关键步骤标注:
@Aspect:标记该类为一个切面类@Component:让Spring管理该类的Bean@Pointcut:定义切入点表达式,筛选需要增强的方法@Around:环绕通知,可控制目标方法的执行全过程joinPoint.proceed():调用原始业务方法,是环绕通知的关键
六、底层原理:Spring AOP如何工作?
核心依赖: Spring AOP底层依赖动态代理 + BeanPostProcessor(Bean后置处理器)。
代理创建流程:
Spring容器启动,扫描所有Bean
BeanPostProcessor在Bean初始化前后进行处理AbstractAutoProxyCreator检查Bean是否需要被AOP代理(是否匹配任何切入点)如果需要代理,
ProxyFactory根据目标类是否实现接口自动选择代理方式:有接口 → JDK动态代理(
JdkDynamicAopProxy)无接口 → CGLIB动态代理(
CglibAopProxy)
生成代理对象并放入容器,替代原始Bean
技术支撑:
反射机制:JDK动态代理的核心,运行时动态调用方法
ASM字节码技术:CGLIB动态代理的基础,运行时动态生成字节码
类加载器:动态生成的代理类需要加载到JVM方法区
七、高频面试题与参考答案
题目1:什么是AOP?它解决了什么问题?
参考答案:
AOP全称Aspect Oriented Programming,面向切面编程,是Spring核心两大思想之一。它通过将日志、事务、权限等横切关注点从业务逻辑中剥离出来,封装成可复用的切面,再通过动态代理技术在运行时将切面逻辑“织入”到目标方法中。核心价值在于减少代码重复、降低模块耦合度。
💡 踩分点:横切关注点、切面、动态代理、织入、代码重复、解耦
题目2:Spring AOP的底层实现原理是什么?JDK动态代理和CGLIB有什么区别?
参考答案:
Spring AOP底层依赖动态代理技术,默认优先使用JDK动态代理(目标类实现接口时),否则回退到CGLIB代理(目标类无接口时)-2。
| 对比点 | JDK动态代理 | CGLIB |
|---|---|---|
| 实现原理 | 基于反射,生成接口代理类 | 基于ASM字节码,生成子类代理 |
| 依赖条件 | 目标类必须实现接口 | 不需要接口,但不能是final类/方法 |
| 性能 | 代理创建快,调用略慢 | 代理创建慢,调用更快 |
💡 踩分点:动态代理、JDK代理的接口要求、CGLIB的子类继承、Spring的自动选择机制
题目3:@Transactional注解在什么情况下会失效?
参考答案:
事务失效的核心原因是:事务靠AOP代理实现,凡是绕过代理或代理机制搞不定的情况,事务都会失效-36。常见失效场景:
内部调用:同类中直接调用
this.method(),绕过了代理对象非public方法:
@Transactional只对public方法生效异常被catch吞掉:事务不会回滚
rollbackFor未正确配置:默认只回滚RuntimeException,检查异常不会回滚
💡 踩分点:AOP代理、内部调用、public限制、异常回滚规则
题目4:Spring AOP和AspectJ有什么区别?
参考答案:
Spring AOP:Spring自己实现的AOP框架,基于动态代理,仅支持运行时织入,且只支持方法级别的连接点-。
AspectJ:功能更强大的AOP框架,支持编译时、类加载时、运行时织入,支持字段、构造函数等更多连接点。
Spring AOP默认使用AspectJ的注解语法(
@Aspect),但底层仍是动态代理实现。
💡 踩分点:织入时机、连接点范围、注解兼容性
八、结尾总结
核心知识点回顾:
AOP本质:一种解决横切关注点模块化的编程范式
AOP vs 代理模式:AOP是思想,动态代理是实现手段
两种代理方式:JDK动态代理(基于接口)vs CGLIB(基于继承)
Spring AOP工作流程:IoC启动 → BeanPostProcessor检测 → ProxyFactory选择代理方式 → 生成代理对象
重点提醒:
✅ 记住:AOP ≠ 动态代理,动态代理是实现AOP的技术之一
❌ 易错:静态代理在编译期确定,动态代理在运行时生成,别混淆
⚠️ 面试重点:JDK vs CGLIB的区别、
@Transactional失效场景
进阶学习方向: 下一篇我们将深入探讨Spring事务传播行为与隔离级别,敬请关注。
