跳转至

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 默认只对 RuntimeExceptionError 回滚,受检异常不回滚。用 rollbackFor = Exception.class

七、AOP 应用场景

  • 声明式事务 @Transactional
  • 日志、审计
  • 权限校验
  • 性能监控(方法耗时)
  • 分布式链路追踪埋点

高频追问

  • Spring AOP vs AspectJ:Spring AOP 是运行时代理,只支持方法级;AspectJ 是编译期/类加载期织入,支持字段、构造器等。
  • 为什么 CGLIB 不能代理 final?因为它是生成子类重写方法。