1. AOP 的核心概念?

AOP(面向切面编程):把"横切关注点"(日志、事务、权限、性能统计)从业务代码中抽取出来,在运行时织入。核心术语:

@Around 环绕通知模板
@Aspect
@Component
class LogAspect {

    @Around("execution(* com.demo.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            Object result = pjp.proceed();          // 放行:执行目标方法
            System.out.println("耗时 " + (System.currentTimeMillis() - start) + "ms");
            return result;
        } catch (Exception e) {
            System.out.println("异常: " + e.getMessage());
            throw e;                            // 异常要重新抛出
        }
    }
}

🎯 面试要点

  • Spring AOP 只支持方法级别的连接点(拦截器链基于动态代理);AspectJ 原生还支持字段/构造器(编译期织入)
  • 切点表达式常用:execution(修饰符 返回类型 类.方法(参数))、within(包)、@annotation(注解名)
  • @Around 中 proceed() 可以调用 0 次、1 次或多次(Spring 官方文档:"may be invoked once, many times, or not at all … All of these are legal")——多次调用会让目标方法执行多次;忘调则目标方法不执行

2. Spring AOP 用 JDK 代理还是 CGLIB?怎么决定?

代理创建的核心:AbstractAutoProxyCreator(BeanPostProcessor)在 Bean 初始化后检测是否有匹配切点 → 有则创建代理对象替换原 Bean。所以代理对象本身就是容器里的那个 Bean。

🎯 面试要点

  • Spring AOP 是基于运行时代理的轻量 AOP;AspectJ 是编译期/加载期织入,更强但需要特殊编译器
  • Spring 的 @Aspect 用的是 AspectJ 的注解语法,但执行机制仍是 Spring 自己的动态代理
  • proxyTargetClass=true 强制 CGLIB;Spring Boot 默认即 CGLIB

3. 为什么 AOP/事务对"方法内部自调用"失效?(经典坑)

代理切面只对外部调用(经过代理对象)生效。类内部 this.method() 调用的是原始对象的方法,不走代理 → 切面不执行 → 事务注解失效。

失效与解决
@Service
class OrderService {
    @Transactional
    public void a() {
        this.b();            // ❌ this 是原始对象,b() 的事务不生效
    }

    @Transactional
    public void b() { ... }
}

// 解决方式:
// 1. 注入自身代理(Spring 构造器注入循环问题注意)
@Autowired private OrderService self;   self.b();

// 2. AopContext.currentProxy()(需 @EnableAspectJAutoProxy(exposeProxy=true))
((OrderService) AopContext.currentProxy()).b();

// 3. 拆到另一个 Bean(最干净,推荐)

🎯 面试要点

  • 所有基于代理的增强(事务、缓存 @Cacheable、异步 @Async、自定义切面)都有自调用问题
  • 根治:把内层逻辑拆到独立 Bean 中调用

4. 多个切面/通知的执行顺序?

🎯 面试要点

  • 旧版本(<5.2.7)After 可能在 AfterReturning 之前——新语义已统一,面试按新版答
  • 事务切面一般 @Order 较低(内层),业务日志切面外层

🎤 常见面试追问

  1. Spring AOP 和 AspectJ 的区别?——Spring AOP 是运行期动态代理(只能代理方法,简单轻量);AspectJ 是编译期/加载期织入(能织字段/构造器,更强)。Spring 只借用了 AspectJ 的注解语法。
  2. @Transactional 为什么对内部方法调用失效?——this 调用走的是原始对象不是代理对象,切面不生效。解决:注入自身代理 / AopContext.currentProxy() / 拆到独立 Bean。
  3. 切点表达式 execution 怎么写?——execution(修饰符 返回类型 包.类.方法(参数)),如 execution(* com.demo.service.*.*(..)) 匹配 service 包下所有方法。
  4. @Around 和 @Before/@After 的区别?——@Around 能控制方法是否执行(proceed())、修改参数和返回值,最强大;@Before/@After 只是"通知时机"。
  5. 多个切面的顺序怎么控制?——@Order 注解(值越小越先执行):@Order(1) 的切面先进入(环绕外层),后退出。

📖 名词解释(本页术语)

术语 大白话解释
AOP(面向切面编程)把"横切逻辑"(日志/事务/权限)从业务代码抽出来统一织入——业务方法不用重复写这些。
切面(Aspect)/ 切点(Pointcut)/ 通知(Advice)切面 = 横切逻辑的模块(@Aspect 类);切点 = 匹配哪些方法的表达式;通知 = 在切点上执行的逻辑(Before/After/Around 等 5 种)。
织入(Weaving)把切面逻辑"缝"进目标方法的过程。Spring 用运行期动态代理织入。
@Around环绕通知:方法执行前后都能干预,通过 ProceedingJoinPoint.proceed() 放行目标方法。
JDK 动态代理 / CGLIB两种代理实现:JDK 代理基于接口(目标必须实现接口);CGLIB 基于继承(生成子类)。Spring Boot 默认 CGLIB。
自调用问题类内部 this.method() 不走代理 → 增强失效。所有基于代理的功能(事务/缓存/异步)都有此坑。
@Order切面执行顺序注解:值越小越先执行(洋葱模型外层)。
⚠️ 本页面由 AI 生成,内容仅供参考,请以官方文档和实际源码为准。