Spring AOP 原理?

2025年 阅读约 15 分钟 面试指南 · Spring Boot

深入解析Spring AOP原理:JDK动态代理 vs CGLIB、切面表达式、通知类型、@Transactional失效场景、AOP执行顺序,附面试模拟问答。

一句话总结

AOP(面向切面编程):将横切关注点(日志、事务、权限)从业务代码中分离,通过动态代理在方法执行前后织入增强逻辑。Spring AOP 默认使用 JDK 动态代理(基于接口)或 CGLIB(基于继承,Spring Boot 2.x 默认)。核心概念:Aspect(切面)= Pointcut(切入点)+ Advice(通知)。通知类型:@Before、@After、@AfterReturning、@AfterThrowing、@Around(最强大,可控制方法执行)。

初级理解

AOP 核心概念

# AOP 术语 # JoinPoint(连接点):所有可以被增强的方法 # Pointcut(切入点):实际被增强的方法(JoinPoint 的子集) # Advice(通知):增强的逻辑(什么时候做什么) # Aspect(切面):Pointcut + Advice 的组合 # Target(目标对象):被代理的对象 # Weaving(织入):将增强应用到目标对象的过程 # 通知类型 @Aspect @Component public class LogAspect { @Before("execution(* com.example.service.*.*(..))") public void before() { } // 方法执行前 @After("execution(* com.example.service.*.*(..))") public void after() { } // 方法执行后(无论是否异常) @AfterReturning("execution(* com.example.service.*.*(..))") public void afterReturning() { } // 正常返回后 @AfterThrowing("execution(* com.example.service.*.*(..))") public void afterThrowing() { } // 异常抛出后 @Around("execution(* com.example.service.*.*(..))") public Object around(ProceedingJoinPoint pjp) throws Throwable { // 前置逻辑 Object result = pjp.proceed(); // 执行目标方法 // 后置逻辑 return result; } }

中级深入

JDK 动态代理 vs CGLIB

对比维度JDK 动态代理CGLIB
原理基于接口,生成实现类基于继承,生成子类
要求必须有接口不能是 final 类/方法
性能创建快,执行略慢创建慢,执行快
Spring 默认有接口时使用无接口时使用(Spring Boot 2.x 默认)
# Spring Boot 2.x 默认使用 CGLIB # 配置切换 spring: aop: proxy-target-class: false # false = JDK 动态代理 # 为什么默认改为 CGLIB? # 1. 不需要强制写接口 # 2. 同一个类内部调用也能被代理(通过 this 调用不行) # 3. 性能更好

切点表达式

# execution 表达式格式 # execution(修饰符 返回值 包名.类名.方法名(参数)) # 常用示例 execution(* com.example.service.*.*(..)) # 任意返回值,service 包下任意类任意方法,任意参数 execution(public * com.example..*.find*(Long)) # public 方法,任意返回值,com.example 及子包,find 开头,Long 参数 # @annotation 切点 @annotation(com.example.annotation.Log) # 标注了 @Log 注解的方法 # 组合切点 @Pointcut("execution(* com.example.service.*.*(..))") public void serviceLayer() {} @Pointcut("@annotation(com.example.annotation.Log)") public void logAnnotation() {} @Around("serviceLayer() && logAnnotation()") public Object around(ProceedingJoinPoint pjp) { }

高级拓展

@Transactional 失效场景

# 场景1:非 public 方法 @Transactional private void save() { } // ❌ 失效!AOP 只能代理 public 方法 # 场景2:同类内部调用 @Service public class UserService { public void methodA() { this.methodB(); // ❌ 失效!this 是原始对象,不是代理 } @Transactional public void methodB() { } } # 解决方案:注入自己 @Service public class UserService { @Autowired private UserService self; public void methodA() { self.methodB(); // ✅ 通过代理调用 } } # 场景3:异常被捕获 @Transactional public void save() { try { // 数据库操作 } catch (Exception e) { log.error("error", e); // ❌ 事务不回滚!异常被吞了 } } # 场景4:非 RuntimeException(默认不回滚) @Transactional public void save() throws Exception { throw new Exception("error"); // ❌ 默认不回滚 checked 异常 } # 指定回滚异常 @Transactional(rollbackFor = Exception.class)

实战场景

场景:自定义日志注解

# 1. 定义注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface OperationLog { String value() default ""; } # 2. 定义切面 @Aspect @Component public class OperationLogAspect { @Around("@annotation(operationLog)") public Object around(ProceedingJoinPoint pjp, OperationLog operationLog) { String desc = operationLog.value(); String method = pjp.getSignature().getName(); Object[] args = pjp.getArgs(); log.info("操作开始:{},方法:{},参数:{}", desc, method, args); long start = System.currentTimeMillis(); try { Object result = pjp.proceed(); log.info("操作成功:{},耗时:{}ms", desc, System.currentTimeMillis() - start); return result; } catch (Throwable e) { log.error("操作失败:{},异常:{}", desc, e.getMessage()); throw new RuntimeException(e); } } } # 3. 使用 @OperationLog("创建用户") public User createUser(UserDTO dto) { }

面试模拟

面试官:Spring AOP 的原理是什么?

你:基于动态代理。如果目标类实现了接口,默认用 JDK 动态代理(生成接口实现类);如果没有接口,用 CGLIB(生成子类)。Spring Boot 2.x 默认使用 CGLIB。AOP 将横切逻辑(日志、事务)从业务代码中分离,通过代理对象在方法执行前后织入增强。

面试官:@Transactional 什么情况下会失效?

你:1)非 public 方法;2)同类内部调用(this 调用不走代理);3)异常被 try-catch 吞掉;4)默认只回滚 RuntimeException,checked 异常不回滚;5)数据库引擎不支持事务(如 MyISAM)。