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 注解将不会生效。
为什么会失效?
- 外部调用通过代理:当你从 Controller 或其他地方调用
myService.methodA()时,你实际上调用的是 Spring 创建的代理对象 (MyServiceProxy) 的methodA方法。此时,AOP 是有效的。 - 内部调用绕过代理:当
methodA的代码开始执行时,它已经进入了原始的目标对象 (MyService的实例) 内部。此时,this关键字指向的是目标对象本身,而不是代理对象。 - 直接调用:因此,
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, protected 或 default (包级私有) 方法上。
@Service
public class MyService {
@Transactional
private void privateMethod() { // AOP 失效
// ...
}
@Transactional
protected void protectedMethod() { // AOP 失效
// ...
}
}
为什么会失效?
- JDK 动态代理:它基于接口实现,只能代理接口中声明的方法,而接口方法都是
public的。 - CGLIB 代理:它通过继承和重写(Override)父类方法来实现代理。
private方法在子类中不可见,无法被重写。protected和default方法虽然可以被子类重写,但 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 方法或 final 类 | CGLIB 基于继承,无法重写 final 方法或继承 final 类。 |
| 切点表达式错误 | AOP 框架根据表达式找不到匹配的方法。 |
手动 new 对象 | 对象脱离了 Spring 容器的管理,无法被代理。 |
1398

被折叠的 条评论
为什么被折叠?



