Spring AOP 面试题¶
一、什么是 AOP¶
AOP(Aspect-Oriented Programming,面向切面编程),是 OOP 的补充,用于横向抽取与业务无关但多个模块共用的逻辑(日志、事务、权限、监控)。
二、核心概念¶
| 术语 | 含义 |
|---|---|
| Aspect(切面) | 横切逻辑的模块,@Aspect |
| Join Point(连接点) | 程序执行点,如方法调用、异常抛出 |
| Pointcut(切点) | 匹配连接点的表达式 |
| Advice(通知) | 在切点做的动作(前置/后置/环绕等) |
| Target(目标对象) | 被代理的对象 |
| Proxy(代理) | AOP 生成的对象 |
| Weaving(织入) | 把切面应用到目标对象并创建代理的过程 |
三、通知类型¶
@Before("execution(* com.example.service.*.*(..))")
public void before(JoinPoint jp) { ... }
@AfterReturning(pointcut="...", returning="result")
public void afterReturning(Object result) { ... }
@AfterThrowing(pointcut="...", throwing="e")
public void afterThrowing(Exception e) { ... }
@After("...") // 最终通知(finally)
public void after() { ... }
@Around("...")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// 前置
Object ret = pjp.proceed();
// 后置
return ret;
}
四、JDK 动态代理 vs CGLIB¶
| JDK 动态代理 | CGLIB | |
|---|---|---|
| 原理 | 基于接口,反射生成实现类 | 基于继承,生成子类 |
| 要求 | 目标类必须实现接口 | 不能代理 final 类/方法 |
| 性能 | 创建快,执行慢 | 创建慢,执行快(FastClass) |
| 默认 | Spring 5.x 有接口用 JDK | Spring Boot 2.x 默认 CGLIB |
// JDK
Proxy.newProxyInstance(classLoader, interfaces, handler);
// CGLIB
Enhancer.create(targetClass, callback);
五、切点表达式¶
execution(权限修饰符 返回值 包.类.方法(参数) 异常)
// 示例
execution(* com.example.service..*.*(..))
// 任意返回值,service 包及子包下所有类所有方法
@annotation(org.springframework.transaction.annotation.Transactional)
// 匹配带 @Transactional 的方法
六、AOP 的常见坑¶
1. 自调用失效¶
@Service
public class OrderService {
public void create() {
this.doCreate(); // this 是目标对象本身,不是代理,AOP 失效!
}
@Transactional
public void doCreate() { ... }
}
解决:
- 注入自身:@Autowired private OrderService self; 然后 self.doCreate()。
- AopContext.currentProxy()(需要 @EnableAspectJAutoProxy(exposeProxy = true))。
2. 同类内部方法调用、private 方法不生效¶
CGLIB 是子类代理,private 方法无法重写;JDK 代理只能代理接口方法。
3. 异常回滚失效¶
@Transactional 默认只对 RuntimeException 和 Error 回滚,受检异常不回滚。用 rollbackFor = Exception.class。
七、AOP 应用场景¶
- 声明式事务
@Transactional - 日志、审计
- 权限校验
- 性能监控(方法耗时)
- 分布式链路追踪埋点
高频追问
- Spring AOP vs AspectJ:Spring AOP 是运行时代理,只支持方法级;AspectJ 是编译期/类加载期织入,支持字段、构造器等。
- 为什么 CGLIB 不能代理 final?因为它是生成子类重写方法。