AOP 失效的场景有哪些?为什么会失效?

AOP 失效是 Spring 开发中一个非常经典且常见的“坑”,理解其背后的原理至关重要。失效的根本原因在于:AOP 是通过动态代理实现的,只有当方法调用通过代理对象发起时,切面逻辑才能被织入。

以下是 AOP 失效的最主要场景,以及它们失效的原因。

1. 方法内部的“自我调用” (Most Common Scenario)

这是最常见、最容易遇到的失效场景。

场景描述

在一个被 AOP 代理的类中,一个方法 methodA() 调用了同一个类中的另一个被 AOP 注解标记的方法 methodB(),但是通过 this 关键字来调用。

@Service
public class MyService {

    public void methodA() {
        System.out.println("--- Executing method A ---");
        // 关键:通过 this 调用,AOP 将会失效!
        this.methodB(); 
    }

    @Transactional // 或者任何自定义的 AOP 注解
    public void methodB() {
        System.out.println("--- Executing method B ---");
    }
}

当你从外部调用 myService.methodA() 时,methodB() 上的 @Transactional 注解将不会生效。

为什么会失效?
  1. 外部调用通过代理:当你从 Controller 或其他地方调用 myService.methodA() 时,你实际上调用的是 Spring 创建的代理对象 (MyServiceProxy) 的 methodA 方法。此时,AOP 是有效的。
  2. 内部调用绕过代理:当 methodA 的代码开始执行时,它已经进入了原始的目标对象 (MyService 的实例) 内部。此时,this 关键字指向的是目标对象本身,而不是代理对象。
  3. 直接调用:因此,this.methodB() 这行代码就变成了目标对象内部的一个普通方法调用,它直接跳转到了 methodB 的字节码,完全绕过了代理对象。既然没有经过代理对象,那么代理对象上附加的AOP增强逻辑(如事务管理)自然就不会被执行。

图解:

外部调用  ->  MyServiceProxy.methodA()  ->  [AOP增强]  ->  Target(MyService).methodA()
                                                           |
                                                           +-> this.methodB()  (直接调用,绕过代理) -> Target(MyService).methodB()
                                                                                                      (AOP 失效)
如何解决?
  • 方法一:注入自身代理 (推荐)
    在类中注入它自身的代理对象,然后通过代理对象来调用方法。

    @Service
    public class MyService {
        @Autowired
        private MyService self; // 注入自身的代理对象
    
        public void methodA() {
            // ...
            self.methodB(); // 通过代理对象调用
        }
    
        @Transactional
        public void methodB() {
            // ...
        }
    }
    

    注意:这可能会导致循环依赖问题。你需要确保 Spring Boot 版本较高(2.6+ 默认禁止循环依赖)或已开启相关配置。

  • 方法二:使用 AopContext.currentProxy()
    获取当前线程绑定的代理对象。需要开启 expose-proxy=true

    @Service
    @EnableAspectJAutoProxy(exposeProxy = true) // 必须开启
    public class MyService {
        public void methodA() {
            // ...
            ((MyService)AopContext.currentProxy()).methodB();
        }
        // ...
    }
    

    这种方式与 Spring AOP 强耦合,略显笨重。

  • 方法三:重构代码
    将需要 AOP 的方法(如 methodB)拆分到另一个独立的 Service 类中,然后在 MyService 中注入并调用这个新的 Service。这是最清晰、最符合设计原则的方式。

2. 非 public 方法

场景描述

AOP 注解被用在 private, protecteddefault (包级私有) 方法上。

@Service
public class MyService {
    @Transactional
    private void privateMethod() { // AOP 失效
        // ...
    }

    @Transactional
    protected void protectedMethod() { // AOP 失效
        // ...
    }
}
为什么会失效?
  • JDK 动态代理:它基于接口实现,只能代理接口中声明的方法,而接口方法都是 public 的。
  • CGLIB 代理:它通过继承和重写(Override)父类方法来实现代理。private 方法在子类中不可见,无法被重写。protecteddefault 方法虽然可以被子类重写,但 Spring AOP 的设计出于规范和一致性的考虑,默认也只拦截 public 方法。

结论:Spring AOP 默认只对 public 方法进行增强。

3. final 方法或 final

场景描述

AOP 注解用在 final 方法上,或者一个类被声明为 final

@Service
public final class FinalService { // CGLIB 无法继承 final 类
    // ...
}

@Service
public class MyService {
    @Transactional
    public final void finalMethod() { // CGLIB 无法重写 final 方法
        // ...
    }
}
为什么会失效?

这主要与 CGLIB 的工作原理有关。

  • CGLIB 通过创建目标类的子类来实现代理。
  • Java 语法规定,final 类不能被继承,final 方法不能被子类重写。
  • 因此,CGLIB 无法为 final 类创建代理,也无法重写 final 方法来织入增强逻辑。
  • 即使目标类实现了接口,如果 Spring Boot 配置强制使用 CGLIB (默认行为),AOP 依然会失效。

4. 切点表达式未匹配到方法

场景描述

@Pointcut 中定义的表达式写错了,导致它没有正确地匹配到你想要增强的方法。

@Aspect
@Component
public class MyAspect {
    // 表达式错误:包名写错,或者方法签名不匹配
    @Pointcut("execution(* com.example.wrongpackage..*.*(..))")
    public void myPointcut() {}

    @Before("myPointcut()")
    public void doBefore() {
        // 这个通知永远不会被执行
    }
}
为什么会失效?

这是最直接的原因:AOP 框架根据你的切点表达式找不到任何匹配的连接点(方法),所以它认为没有任何方法需要被增强,自然也就不会创建代理或应用通知。

5. new 关键字创建对象

场景描述

在代码中手动 new 一个对象,而不是通过 Spring 容器注入 (@Autowired)。

@RestController
public class MyController {
    public void handleRequest() {
        // 手动 new 的对象,不受 Spring 容器管理
        MyService service = new MyService();
        service.methodB(); // AOP 失效
    }
}
为什么会失效?
  • Spring AOP 是在 Bean 的生命周期中起作用的。当一个 Bean 被 Spring 容器实例化和初始化时,Spring 才有机会检查它是否需要被代理,并用代理对象替换它。
  • 通过 new 关键字创建的对象是一个普通的 Java 对象,它完全脱离了 Spring 容器的管理。Spring 对它一无所知,自然也无法为它创建代理并应用 AOP 逻辑。

总结

失效场景根本原因
方法内部自调用 (this.xxx())调用绕过了代理对象,直接在目标对象内部发生。
public 方法Spring AOP 的设计规范,默认只拦截 public 方法。
final 方法或 finalCGLIB 基于继承,无法重写 final 方法或继承 final 类。
切点表达式错误AOP 框架根据表达式找不到匹配的方法。
手动 new 对象对象脱离了 Spring 容器的管理,无法被代理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰糖心书房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值