刚刚遇到了Spring AOP失效的问题,趁这个机会总结一下~

什么是Spring AOP

Spring AOP是Spring框架中的一个重要组成部分,它提供了一种面向切面编程(Aspect-Oriented Programming, AOP)的实现方式。AOP的核心思想是将那些分布在多个对象或方法中的共同行为(即横切关注点)提取出来,形成一个独立的模块,称为“切面”(Aspect)。这样做的好处是可以增强的代码模块化,减少重复代码,提高系统的可维护性和可扩展性。

AOP基于什么实现

Spring AOP 的实现主要基于动态代理和字节码增强两种技术。

首先,Spring AOP利用动态代理技术在运行时生成代理对象,这些代理对象能够拦截对目标对象的调用,并在调用前后执行切面逻辑。具体来说,如果目标对象实现了接口,Spring AOP会使用JDK动态代理来生成代理对象;而如果目标对象没有实现接口,Spring AOP则会使用CGLIB动态代理来生成代理对象。

其次,Spring AOP还利用了AspectJ框架来实现字节码增强。这是一种在编译时或运行时修改目标对象的字节码的技术,以此来插入切面的逻辑。通过这种方式,即使不改变源代码,也能够为程序添加额外的功能。

AOP在什么场景下会失效

虽然Spring的AOP在大多数情况下都是有效的,但在某些场景下可能会失效。下面来分析Spring AOP失效的常见场景。

  1. 非Spring管理的对象
  2. 同一个Bean内部的方法调用(遇到的正是这个问题qwq)
  3. 静态方法
  4. 内部类
  5. 方法访问修饰符
  6. 异步方法
  7. Spring版本兼容性问题
  8. Transactional注解
  9. 异常处理
  10. ApplicationContext未注入

1. 非Spring管理的对象

Spring的AOP只能拦截由Spring容器管理的Bean对象。如果您使用了非受Spring管理的对象,则AOP将无法对其进行拦截。

2.同一个Bean内部的方法调用

如果一个Bean内部的方法直接调用同一个Bean内部的另一个方法,AOP将无法拦截这个内部方法调用。因为AOP是基于代理的,只有通过代理对象才能触发AOP拦截。

假设我们有一个名为 MyBean 的类,其中包含了两个方法 method1()method2()。在 method1() 中,直接调用了 method2() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class MyBean {

public void method1() {
System.out.println("Inside method1");
method2(); // 直接调用同一个 Bean 内部的另一个方法
}

public void method2() {
System.out.println("Inside method2");
}
}

现在,让我们创建一个切面来拦截 method1() 的执行,并打印一些日志信息:

1
2
3
4
5
6
7
8
9
@Aspect
@Component
public class MyAspect {

@Before("execution(* com.example.MyBean.method1())")
public void beforeMethod1() {
System.out.println("Before method1 execution");
}
}

上述切面使用 @Before 注解来定义了一个前置通知,在执行 MyBean 类的 method1() 方法之前被触发。

然后,我们在 Spring 应用程序中使用这两个组件:

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
public class MyApplication {

public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);

MyBean myBean = ApplicationContext.getBean(MyBean.class);
myBean.method1();
}
}

当我们运行应用程序时,我们会发现 “Before method1 execution” 这条日志信息被打印出来,但是 “Inside method2” 这条日志信息却没有被打印出来。这是因为 AOP 无法拦截 method2() 的直接调用,而只能拦截通过代理对象触发的方法调用。

3.静态方法

AOP不能代理静态方法,因为静态方法属于类级别,而不是实例级别,所以无法通过代理机制进行拦截。

4.内部类

如果目标方法是内部类的实例方法,那么AOP也无法进行代理,因为内部类的实例化是在外围类的实例化之后,此时AOP代理已经创建完成。

5.方法访问修饰符

如果目标方法是private或final的,AOP将无法对其进行代理,因为这些方法无法被子类覆盖或外部访问。

6.异步方法

对于使用Spring的异步特性(如@Async注解)的方法,AOP拦截器可能无法正常工作。这是因为异步方法在运行时会创建新的线程或使用线程池,AOP拦截器无法跟踪到这些新线程中的方法调用。

7.Spring版本兼容性问题

不同版本的Spring框架可能在AOP的实现上存在差异,如果遇到AOP失效的问题,也需要考虑是否是由于Spring版本升级导致的兼容性问题。

8.Transactional注解

在使用@Transactional注解进行事务管理时,如果在同一个类中的方法调用另一个带有@Transactional注解的方法,那么内部调用可能不会触发AOP代理,因为默认使用的是this引用而不是代理对象。

9.异常处理

如果在目标方法中捕获了所有异常,而AOP中的环绕通知(around advice)抛出了异常,那么这个异常可能会被目标方法中的异常处理逻辑所吞没,导致AOP失效。

10.ApplicationContext未注入

如果需要在Service层使用AOP,并且Service实现了ApplicationContextAware接口,但没有正确注入ApplicationContext,那么AOP也可能失效。确保在Service实现类中正确注入ApplicationContext,以便能够获取到代理对象。

参考