Skip to content

Spring AOP

本文介绍有关Spring AOP的知识。

1. 什么是AOP

AOP,全称Aspect-Oriented Programming,面向切面编程,是对OOP(Object-Oriented Programming,面向对象编程)的补充。

说人话,看不懂。

举个例子,假设现在有个用户服务类,其中有三个方法:用户注册、用户登录和用户退出登录。在每个方法调用时,都需要写日志,便于运维。如果我们在开发中,每次都需要手动加上写日志的逻辑,那么代码不仅变得重复冗余,而且不利于修改。

java
@Service
@Slf4j
public class UserService {

    /**
     * 用户注册
     */
    public void registerUser(String username, String password){
        log.info("registerUser {} {}", username, password);
        // 注册逻辑
    }

    /**
     * 用户登录
     */
    public void userLogin(String username, String password){
        log.info("userLogin {} {}", username, password);
        // 登录逻辑
    }


    /**
     * 用户退出登录 
     */
    public void userLogout(String userId){
        log.info("userLogout {}", userId);
        // 退出登录逻辑
    }

     /**
     * 这是一个工具方法,不应该记录日志
     */
    private void generateUserId(){
        
    }
}

那有没有一种机制,能让我们在调用方法前,先执行一段代码去写日志,这样就只需要指定哪些方法需要写日志了,而不用每次手动添加写日志的逻辑。也就是说,我们只需要在程序中声明:当程序运行UserService.registerUser()UserService.userLogin()UserService.userLogout()前,先写日志log()。这就是AOP机制。

最终执行代码可能会如下:

java
LogUtil.log();
UserService.registerUser()

2. AOP术语

在AOP领域中,存在一些关键术语:

  • 目标对象(Target):也就是说哪些对象中的某方法被执行时,需要添加其他额外逻辑,如上面的UserService对象;

  • 连接点(Join Point):在程序执行中的某个时间点,或者说某个事件发生时,需要添加其他额外逻辑,例如上面的调用registerUser()userLogin()userLogout()时,需要执行写日志操作;

    常见的连接点包括方法执行、字段访问、构造函数调用或异常处理

  • 切入点(Pointcut):匹配特定连接点的表达式。只有特定的连接点才会触发AOP,也就是说,目标对象中的哪些方法被调用时,才会触发AOP,在上面的例子中,调用registerUser()userLogin()userLogout()时,需要执行写日志,调用generateUserId()时,不需要写日志。

  • 通知(Advice):当匹配上连接点后,需要执行什么操作,通知是实际执行的代码块,并且需要和切入点一起使用。如上面的log()方法;

  • 切面(Aspect):切面是一个类,包含多个通知,如上面的LogUtil.log()中的LogUtil类;

  • 织入(Weaving):在应用程序声明周期的什么时候实现AOP,编译期、类加载期还是运行期;

如果连接点是方法执行,那么可以细分为以下时间点:

  • 方法执行前
  • 方法正常执行后
  • 方法报错后
  • 方法最终返回前

在Java中,可以使用try-catch-finally块来理解:

java
try{
  方法执行前,这里可以添加操作(方法执行前)
  目标对象目标方法执行
  方法执行后,这里可以添加操作(方法正常执行后)
}catch(){
  方法报错后,这里可以添加操作(方法报错后)
}finally{
  (方法最终返回前)
}

3. AspectJ 与 Spring AOP

3.1 AspectJ介绍

AspectJ是一个完整的 AOP 解决方案,提供灵活且强大的织入机制,直接修改字节码 。官方地址:https://eclipse.dev/aspectj/

实现方式分为两种:

  • 编译时实现:在编译时,AspectJ 编译器(ajc)将目标对象类和切面类的源代码作为输入,并生成织入后的类文件作为输出 。 例如,最终生成的UserService.classregisterUser()方法如下:

    java
    public void registerUser(String username, String password){
        LogUtil.log();
        // 注册逻辑
    }
  • 类加载时实现:正常编译,但是将字节码文件文件加载进JVM内存时,修改字节码,添加通知。

    例如,在UserService.class文件中,registerUser()方法正常:

    java
    public void registerUser(String username, String password){
        // 注册逻辑
    }

    但是,将UserService.class文件加载进内存时,修改UserService代码实现,在内存中的代码变为如下:

    java
    public void registerUser(String username, String password){
        LogUtil.log();
        // 注册逻辑
    }

AspectJ 5 引入了 @AspectJ 样式,允许开发者使用带有注解的常规 Java 类来定义切面:

  • 关键注解包括用于声明切面类的 @Aspect、用于定义切入点表达式的 @Pointcut,以及用于通知类型的特定注解(@Before@AfterReturning@AfterThrowing@After@Around)。
  • 这些注解定义在 org.aspectj.lang.annotation 包中 。

3.2 Spring AOP介绍

Spring AOP是另一种AOP实现,它不直接修改目标类的字节码,而是创建包装目标对象的代理对象。代理分为:

  • JDK代理:由Java自带的代理机制,原理是实现目标对象相同的接口,聚合目标对象;
  • CGLIB代理:需要CGLIB支持,原理是继承目标对象类;

3.3 AspectJ和Spring AOP的关系

Spring AOP 的核心实现基于 JDK 动态代理和 CGLIB,是一种纯 Java 解决方案,其实现机制不依赖于AspectJ。

但是,Spring AOP 默认利用 AspectJ 切入点表达式语言来定义切入点,它使得 Spring AOP 能够受益于 AspectJ 强大且富有表现力的语法来匹配连接点,并且,Spring AOP也会实现AspectJ 5中引入的@AspectJ样式。

可以理解为,Spring AOP使用了AspectJ的一些工具方法、注解,所以Spring AOP依赖于AspectJ,但核心实现机制不同。

3.4 Spring AOP与AspectJ的对比

特性Spring AOPAspectJ
实现方式基于代理(JDK动态代理或CGLIB)字节码操作
织入机制运行时织入编译时、编译后、加载时织入
支持的连接点仅限于方法执行连接点支持所有连接点(包括方法执行、字段访问、构造函数调用等)
性能较慢(由于运行时代理开销)较快(字节码直接修改,没有运行时代理开销)
复杂性设置和使用更简单设置更复杂(需要AspectJ编译器或LTW配置)
适用范围仅限Spring管理的Bean可适用所有领域对象(包括非Spring管理的对象)
构建过程无需额外编译器或织入器,可以与常规Java构建集成需要AspectJ编译器(ajc)或LTW代理
自调用问题存在(内部方法调用会绕过代理)不存在(字节码直接织入)
调试与可维护性配置简单,易于启用/禁用;堆栈跟踪可能比较长更改需要重新编译;织入点追踪可能更复杂

重点关注实现方式、织入机制和支持的连接点这三项

4. AOP初体验

4.1 引入依赖

首先引入Spring AOP依赖:

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

查看spring-boot-starter-aop项目依赖,会发现自动引入了aspectj

xml
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>3.5.3</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>6.2.8</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.24</version>
    <scope>compile</scope>
  </dependency>
</dependencies>

4.2 启用AspectJ

使用注解@EnableAspectJAutoProxy在任意配置类上,以启用@AspectJ样式:

java
@Configuration
@ComponentScan(basePackages = {"com.lee.aop.t1"})
@EnableAspectJAutoProxy
public class Config {
    
}

@EnableAspectJAutoProxy注解有两个参数:

  • boolean proxyTargetClass() default false:用于控制 Spring AOP 创建代理的方式

    • proxyTargetClass = false (默认值):

    • Spring AOP 优先使用 JDK 动态代理。如果目标类没有实现任何接口,Spring 会自动退回到使用 CGLIB 代理。

    • 使用 JDK 动态代理时,代理类和目标类是兄弟关系,代理类实现了目标类所实现的接口。

    • proxyTargetClass = true:

      • Spring AOP 强制使用 CGLIB 代理。CGLIB 代理不需要目标类实现接口,它通过继承目标类来创建代理类。
      • 使用 CGLIB 代理时,代理类是目标类的子类。
  • boolean exposeProxy() default false:当将其设置为 true 时,它的作用是:

    • 暴露当前代理对象到 AopContext

    • 解决代理对象内部方法调用无法被 AOP 拦截的问题。

    如果一个 bean 的某个方法 内部调用了自身 的另一个方法,那么代理不会生效:

    java
    @Service
    public class MyService {
    
        @Transactional // 外部调用 methodA 时会走事务
        public void methodA() {
            // ... 一些业务逻辑
            methodB(); // 内部调用 methodB
        }
    
        @Transactional // 希望 methodB 也能有事务
        public void methodB() {
            // ... 另一些业务逻辑
        }
    }

    在这种情况下,当 methodA() 被外部调用时,会通过代理对象进入 AOP 拦截,事务会生效。但是,在 methodA() 内部调用 methodB() 时,这个调用是直接通过 this 指针进行的,而不是通过代理对象。这意味着 methodB() 上的 @Transactional 注解(或其他 AOP 切面)将不会生效,因为它绕过了代理。

    为了解决这个问题,可以将 exposeProxy 设置为 true。当 exposeProxytrue 时,Spring 会将当前线程中的代理对象暴露到 AopContext 中。这样,在 methodA() 内部,就可以通过 AopContext.currentProxy() 获取到当前的代理对象,然后通过代理对象来调用 methodB(),从而让 AOP 切面生效:

    java
    @Service
    public class MyService {
    
        @Transactional
        public void methodA() {
            // ... 一些业务逻辑
            ((MyService) AopContext.currentProxy()).methodB(); // 通过代理调用 methodB
        }
    
        @Transactional
        public void methodB() {
            // ... 另一些业务逻辑
        }
    }

4.3 编写切面类

编写切面类如下:

java
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class Aspect1 {

    @Before("execution(* study())")
    public void before(){
        System.out.println("before");
    }

    @After("execution(* study())")
    public void after(){
        System.out.println("after");
    }

    @AfterThrowing("execution(* study())")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }

    @AfterReturning("execution(* study())")
    public void afterReturning(){
        System.out.println("afterReturning");
    }

}
  • @Aspect:表明该类是切面类;

  • @Before@After@AfterThrowing@AfterReturning这些注解表明该方法是通知;

    执行顺序如下:

    java
    try{
      @Before
      method()
      @AfterReturning
    }catch(){
      @AfterThrowing
    }finally{
      @After
    }

    即存在如下两种执行流程:

    txt
    正常执行: @Before ---> @AfterReturning  ---> @After
    异常执行: @Before ---> @AfterThrowing ---> @After
  • execution(* study()):是切入点表达式,表明该通知匹配哪些方法;

4.4 编写业务类

java
@Component
public class Student {
    public int study() {
        System.out.println("正在学习...");
        return 1;
    }
}

4.5 编写测试类

java
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(Config.class);

        applicationContext.refresh();

        for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>");
        Student bean = applicationContext.getBean(Student.class);
        bean.study();
    }
}

结果如下:

java
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
config
aspect1
student
org.springframework.aop.config.internalAutoProxyCreator
>>>>>>>>>>>>>>>>>>>>>>>
before
正在学习...
afterReturning
after

可以看到,AOP正常发挥作用,并且,容器中多了一个名为org.springframework.aop.config.internalAutoProxyCreator的Bean。

4.6 环绕通知

环绕通知通过@Around实现,主要为了手动控制连接点的执行,通过ProceedingJoinPoint来获取连接点(方法)并执行:

java
@Component
@Aspect
public class Aspect2 {

    @Around("execution(* study())")
    public Object around(ProceedingJoinPoint proceedingJoinPoint){
        try {
            System.out.println("before...");
            Object proceed = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
            System.out.println("after returning...");
            return proceed;
        } catch (Throwable e) {
            System.out.println("after throwing...");
            throw new RuntimeException(e);
        } finally {
            System.out.println("after");
        }
    }

}

5. Spring AOP APIs

本小节介绍Spring AOP中比较底层的API。

5.1 PointCut

PointCut接口代表切入点的实现,在Spring中,由于连接点只有方法调用,所以切入点只关注两点:

  • 哪些类可以被切入;
  • 类中的哪些方法可以被切入;

所以接口定义如下:

java
public interface Pointcut {
	// ClassFilter 决定哪些类可以被切入
	ClassFilter getClassFilter();
  // MethodMatcher 决定类中的哪些方法可以被切入
	MethodMatcher getMethodMatcher();
}
java
public interface ClassFilter {
	boolean matches(Class clazz);
}
java
public interface MethodMatcher {
	// 暂时只关注这个方法,用于静态切点
	boolean matches(Method m, Class<?> targetClass);

	boolean isRuntime();

	boolean matches(Method m, Class<?> targetClass, Object... args);
}

在实际中,用得比较多的是PointCut实现是AspectJExpressionPointcut,它可以设置AspectJ表达式,来判断是否匹配方法:

java
public class Main {
    public static void main(String[] args) throws NoSuchMethodException {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
      	// 设置AspectJ表达式
        pointcut.setExpression("execution(* study())");
      	// 判断是否匹配方法
        boolean match1 = pointcut.matches(Student.class.getDeclaredMethod("study"), Student.class);
        boolean match2 = pointcut.matches(Student.class.getDeclaredMethod("play"), Student.class);

        System.out.println(match1);
        System.out.println(match2);
    }
}

class Student{
    public void study(){}
    public void play(){}
}

// 结果
// true
// false

5.2 Advice与Interceptor

Advice定义了在特定的连接点(Joinpoint)上执行的横切行为(Cross-cutting Concern)。简单来说,它就是你想要插入到程序中特定位置的代码。

根据插入时机的不同,Advice 又分为多种类型,例如:

  • Before Advice (前置通知): 在方法执行前执行。
  • After Returning Advice (返回后通知): 在方法成功执行并返回后执行。
  • After Throwing Advice (抛出异常后通知): 在方法抛出异常后执行。
  • After Advice (后置通知/最终通知): 无论方法是否成功执行或抛出异常,都会在方法执行后执行。
  • Around Advice (环绕通知): 围绕方法执行,可以在方法执行前和执行后都进行操作,甚至可以控制方法是否执行。

InterceptorAdvice 的一种特定类型,特别是环绕通知(Around Advice)的一种实现。它用于拦截运行时事件,例如方法调用、构造函数调用、字段访问等。

最常见的Interceptor是 org.aopalliance.intercept.MethodInterceptor。它提供了一个 invoke(MethodInvocation invocation) 方法,允许在目标方法执行前、执行后以及捕获异常进行操作,MethodInvocation是被拦截的方法,可以通过 invocation.proceed() 来控制是否继续执行目标方法。

这三者的关系如图:

image-20250630125452768
java
package org.aopalliance.aop;

public interface Advice {

}
java
package org.aopalliance.intercept;

import org.aopalliance.aop.Advice;

public interface Interceptor extends Advice {

}
java
package org.aopalliance.intercept;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

@FunctionalInterface
public interface MethodInterceptor extends Interceptor {

	@Nullable
	Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;

}

Advice 奠定了 AOP 的基础,是一个高层抽象,定义了横切关注点的各种“执行时机”,也就是说,如果仅仅在方法执行前打印日志,BeforeAdvice就够了;如果仅仅需要在方法执行后打印日志,AfterAdvice就足够了,从而让我们的程序知道这些Advice是在什么时候执行的。

Interceptor 强大在它能“拦截”整个调用过程,在方法执行前、执行后、甚至决定是否执行方法,或者修改方法的参数和返回值。这是其他简单 Advice (例如BeforeAdvice)类型无法做到的。

在很多时候,仅仅需要简单Advice就足够了,并不总是需要Interceptor

**但是,在Spring中,总是会把Advice转换为Interceptor**执行!!!

在Spring中,针对5种Advice,定义了如下5种实现类:

image-20250630132009409

其中,AspectJMethodBeforeAdviceAspectJAfterReturningAdvice没有实现MethodInterceptor接口,是简单的Advice,所以需要转换器将其转换为MethodInterceptor,分别为MethodBeforeAdviceAdapterAfterReturningAdviceAdapter:

java
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof MethodBeforeAdvice);
	}

	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
		return new MethodBeforeAdviceInterceptor(advice);
	}

}
java
class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {

	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof AfterReturningAdvice);
	}

	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();
		return new AfterReturningAdviceInterceptor(advice);
	}

}

因此,我们可以得出结论,在Spring中,五种Advice的实现都是MethodInterceptor实现,通过invoke()方法控制方法调用流程。

5.3 Advice和PointCut

在前一小节中,Spring中Advice的5种实现都有一个共同父类:AbstractAspectJAdvice,其中包含属性pointcut

java
public AbstractAspectJAdvice(
    Method aspectJAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aspectInstanceFactory) {

  Assert.notNull(aspectJAdviceMethod, "Advice method must not be null");
  this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
  this.methodName = aspectJAdviceMethod.getName();
  this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
  this.aspectJAdviceMethod = aspectJAdviceMethod;
  this.pointcut = pointcut;
  this.aspectInstanceFactory = aspectInstanceFactory;
}

所以,Advice可以聚合PointCut(在某些场景下)。

5.4 Advisor与Aspect

在Spring中,Advisor就是包含一个通知(Advice)的切面,这称为低级切面;在我们程序中,通过@Aspect标注的类中,可以有多个通知(Advice),这称为高级切面

在Spring中,一个高级切换最终会转换为多个低级切面,也就是说,标注了@Before等注解的方法,会转换为一个Advisor

Advisor接口如下:

java
public interface Advisor {
  
	Advice EMPTY_ADVICE = new Advice() {};

	Advice getAdvice();

	default boolean isPerInstance() {
		return true;
	}

}

可以看到,Advisor中需要包含Advice

Advisor的子接口PointCutAdvisor定义如下:

java
public interface PointcutAdvisor extends Advisor {

    Pointcut getPointcut();

}

可以看到,PointcutAdvisor中需要包含PointCut

可以理解为,Advisor同时包含AdvicePointCut

5.5 ProxyFactory

ProxyFactory是Spring通过的代理工厂,它会根据情况提供JDK动态代理或CGLIB代理。

它的使用很简单,只需要提供被代理对象即可:

java
MyService target = new MyService();

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target); // 设置目标对象(被代理对象)

// 得到代理对象
MyService proxy = (MyService)proxyFactory.getProxy();

得到的代理对象,是通过JDK动态代理生成的,还是通过CGLIB生成的,主要判断逻辑如下:

  1. 首先,判断目标对象是否有接口,如果没有接口,那么使用CGLIB生成代理;否则,转到第2步继续判断。关于目标对象是否实现了接口,可以通过以下方法设置:

    java
    proxyFactory.setInterfaces()
  2. 判断属性proxyTargetClass的值:

    • 如果值为true,那么将会使用CGLIB代理;
    • 如果值为false,那么将会使用JDK代理,默认值为false

    可以通过以下方法设置:

    java
    proxyFactory.setProxyTargetClass(true);

通过以上方式创建出来的代理,只是普通的代理对象,没有任何的功能增强,我们可以使用如下方法,为被代理的对象增强方法:

java
proxyFactory.addAdvisor();
// 或者添加多个
// proxyFactory.addAdvisors()

5.6 编程式AOP

下面以编程的方式使用AOP。

首先准备目标类接口和目标类:

java
public interface IService {
    void doSomething(String param);

    String getResult();
}

public class MyService implements IService{
    @Override
    public void doSomething(String param) {
        System.out.println("执行目标方法: doSomething, 参数: " + param);
    }

    @Override
    public String getResult() {
        System.out.println("执行目标方法: getResult");
        return "结果";
    }
}

然后使用AOP:

java
public class Main2 {
    public static void main(String[] args) throws NoSuchMethodException {
        // 1. 创建目标对象
        MyService target = new MyService();

        // 2. 获取Advisor
        Advisor advisor = getAdvisor();

        // 3. 创建代理工厂并配置
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target); // 设置目标对象
        proxyFactory.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
        proxyFactory.addAdvisors(advisor); // 添加 Advisor
        //proxyFactory.setProxyTargetClass(true);
        proxyFactory.setInterfaces(MyService.class.getInterfaces());

        // 4. 获得代理对象并使用
        IService proxy = (IService)proxyFactory.getProxy();
        proxy.doSomething("test");
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>");
        System.out.println(proxy.getResult());
    }

    /**
     * 获得Advisor
     */
    private static Advisor getAdvisor() throws NoSuchMethodException {
        // 1. 创建PointCut
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* getResult())");

        // 2. 创建Advice, 这里使用Spring定义好的AspectJMethodBeforeAdvice,也可以自己实现接口
        AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(
                Advice.class.getDeclaredMethod("before"),
                pointcut,
                new SingletonAspectInstanceFactory(new Advice())
        );

        // 3. 创建Advisor
        DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor(
                pointcut, new MethodBeforeAdviceInterceptor(advice));
        return defaultPointcutAdvisor;
    }

    static class Advice{
        public void before(){
            System.out.println("before...");
        }
    }
}
  • 在第12行中,我们在第一个位置添加了ExposeInvocationInterceptor.ADVISOR,这是因为AspectJMethodBeforeAdvice在调用通知方法时,会从ThreadLocal中获取MethodInvocation,而ExposeInvocationInterceptor.ADVISOR放在Advisor调用链第一个,就是将MethodInvocation设置进ThreadLocal中:

    java
    protected JoinPointMatch getJoinPointMatch() {
      MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
      if (!(mi instanceof ProxyMethodInvocation pmi)) {
        throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
      }
      return getJoinPointMatch(pmi);
    }
  • 在33-36行中,设置advice时需要传入第一个参数Method和第三个参数AspectInstanceFactory,这是为了获取通知方法,以及在哪个对象上调用通知方法;

5.7 MethodInvocation

MethodInvocation表示方法调用,在Spring中,其唯一的实现类是ReflectiveMethodInvocation,通过其代理方法,我们可以得到其中的重要属性:

java
protected ReflectiveMethodInvocation(
    Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments,
    @Nullable Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {

  this.proxy = proxy;  // 代理类
  this.target = target;  // 目标对象
  this.targetClass = targetClass;  // 目标对象类
  this.method = BridgeMethodResolver.findBridgedMethod(method);  // 要执行的目标方法
  this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);  // 目标方法参数
  
  // Advisor链,即通知方法链,用于增强目标方法
  this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
}

在其中的proceed()方法,主要逻辑如下(简化版本,只看主要逻辑):

java
@Override
@Nullable
public Object proceed() throws Throwable {
  // currentInterceptorIndex 表示当前调用的通知方法索引
  if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
    // 如果当前调用的通知方法索引已经到了最后,那么调用目标方法
    return invokeJoinpoint();
  }

  // 获取下一个通知方法
  Object interceptorOrInterceptionAdvice =
      this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
	// 调用下一个通知方法
  return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}

我们以MethodBeforeAdviceInterceptor通知方法中的invoke()为例子:

java
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {

	private final MethodBeforeAdvice advice;

	public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
		Assert.notNull(advice, "Advice must not be null");
		this.advice = advice;
	}

	public Object invoke(MethodInvocation mi) throws Throwable {
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
		return mi.proceed();
	}

}

首先调用通知方法this.advice.before(),然后调用MethodInvocation.proceed(),这就形成了递归调用

5.8 代理对象

我们以JDK代理对象为例子,讲解调用代理对象时,是如何增强目标方法的,查看JdkDynamicAopProxy(这就是代理工厂返回的代理对象),查看其invoke()方法,有以下逻辑:

java
// 获取当前类当前方法符合条件的Advisor链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

// 如果调用链为空,则直接反射调用目标对象方法
if (chain.isEmpty()) {
  Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
  retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
  // 如果调用链不为空,创建MethodInvocation对象,沿着调用链递归调用
  MethodInvocation invocation =
      new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
  retVal = invocation.proceed();
}

6. Spring AOP 源码分析

本小节以查看源码的方式,讲解Spring是如何解析、配置AOP功能的。

6.1 @EnableAspectJAutoProxy

首先查看@EnableAspectJAutoProxy注解,该注解需要加到任意配置类上,以启用AspectJ AOP功能,定义如下:

java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

	boolean proxyTargetClass() default false;

	boolean exposeProxy() default false;

}

可以看到,这个注解注入了AspectJAutoProxyRegistrar,这是一个实现了ImportBeanDefinitionRegistrar接口的类,由ConfigurationClassPostProcessor这个工厂后处理器处理,最终调用registerBeanDefinitions()方法:

java
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy != null) {
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
			}
		}
	}
}

最终会在容器注入以下类:

java
AnnotationAwareAspectJAutoProxyCreator

这个类是Bean后处理器,继承结构如下:

image-20250629203324300

6.2 AnnotationAwareAspectJAutoProxyCreator

AnnotationAwareAspectJAutoProxyCreator这是一个Bean后处理器,主要关注初始化Bean后,查看其父类AbstractAutoProxyCreatorpostProcessAfterInitialization方法:

java
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
  // bean 就是创建完成后的对象
  if (bean != null) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    if (this.earlyBeanReferences.remove(cacheKey) != bean) {
      return wrapIfNecessary(bean, beanName, cacheKey);
    }
  }
  return bean;
}

在第6行wrapIfNecessary()方法中,会判断是否需要为Bean创建代理:

java
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {

  // 省略部分代码...

  // 查看当前bean是否有满足条件的Advisor
  Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
  
  if (specificInterceptors != DO_NOT_PROXY) {
    // 如果有Advisor,那么需要创建代理
    Object proxy = createProxy(
        bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
    return proxy;
  }

  // 没有Advisor,直接返回原始Bean
  return bean;
}

6.3 查找Advisors

通过上面的getAdvicesAndAdvisorsForBean()方法,最终跟踪到findEligibleAdvisors()方法(AbstractAdvisorAutoProxyCreator类中的)

java
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
  // 查找候选的Advisor
  List<Advisor> candidateAdvisors = findCandidateAdvisors();
  // 查找有资格的Advisor,也就是能切入该Bean的Advisor
  List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
  // 扩充Advisors链
  extendAdvisors(eligibleAdvisors);
  
  if (!eligibleAdvisors.isEmpty()) {
    // 如果合格的Advisor链不为空,排序
    try {
      eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    catch (BeanCreationException ex) {
      throw new AopConfigException("Advisor sorting failed with unexpected bean creation, probably due " +
          "to custom use of the Ordered interface. Consider using the @Order annotation instead.", ex);
    }
  }
  return eligibleAdvisors;
}

findCandidateAdvisors()需要查看AnnotationAwareAspectJAutoProxyCreator子类中的实现,主要分为两步:

  • 调用父类中的方法,查找容器中类型为Advisor的Bean,这是直接注入的低级切面;
  • 调用如下方法,解析高级切面(有@Aspect注解的Bean),将其转换为低级切面;
txt
org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors

findAdvisorsThatCanApply()的主要逻辑如下:

  • 获取Bean Class中的每个Method

  • 对于每个Method,用每个Advisor判断是否满足匹配条件,如果满足,则加入到有资格的Advisor列表中;

    类似逻辑为代码如下:

    java
    List<Advisor> eligibleAdvisors = new ArrayList<>();
    
    for (Advisor candidate : candidateAdvisors) {
      MethodMatcher methodMatcher = candidate.getPointCut().getMethodMatcher();
      
      Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
      for (Method method : methods) {
        if (methodMatcher.matches(method, targetClass)) {
          eligibleAdvisors.add(candidate);
        }
      }
    }

extendAdvisors()方法主要是判断Advisor调用链中是否有AspectJ通知,如果有,则需要在调用链第一个位置加入ExposeInvocationInterceptor.ADVISOR

java
public static boolean makeAdvisorChainAspectJCapableIfNecessary(List<Advisor> advisors) {
  // Don't add advisors to an empty list; may indicate that proxying is just not required
  if (!advisors.isEmpty()) {
    boolean foundAspectJAdvice = false;
    for (Advisor advisor : advisors) {
      // Be careful not to get the Advice without a guard, as this might eagerly
      // instantiate a non-singleton AspectJ aspect...
      if (isAspectJAdvice(advisor)) {
        foundAspectJAdvice = true;
        break;
      }
    }
    if (foundAspectJAdvice && !advisors.contains(ExposeInvocationInterceptor.ADVISOR)) {
      advisors.add(0, ExposeInvocationInterceptor.ADVISOR);
      return true;
    }
  }
  return false;
}

6.4 创建代理对象

当上面查找到有符合条件的Advisor后,就需要创建代理对象,最终进入buildProxy()方法,主要逻辑就是创建ProxyFactory,设置代理工厂相关属性(主要是addAdvisors()),最后调用getProxy()方法获得代理对象:

java
private Object buildProxy(Class<?> beanClass, 
                          @Nullable String beanName,
                          @Nullable Object[] specificInterceptors, 
                          TargetSource targetSource, 
                          boolean classOnly) {

  if (this.beanFactory instanceof ConfigurableListableBeanFactory clbf) {
    AutoProxyUtils.exposeTargetClass(clbf, beanName, beanClass);
  }

  ProxyFactory proxyFactory = new ProxyFactory();
  proxyFactory.copyFrom(this);

  if (proxyFactory.isProxyTargetClass()) {
    // Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)
    if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
      // Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
      for (Class<?> ifc : beanClass.getInterfaces()) {
        proxyFactory.addInterface(ifc);
      }
    }
  }
  else {
    // No proxyTargetClass flag enforced, let's apply our default checks...
    if (shouldProxyTargetClass(beanClass, beanName)) {
      proxyFactory.setProxyTargetClass(true);
    }
    else {
      evaluateProxyInterfaces(beanClass, proxyFactory);
    }
  }

  Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
  proxyFactory.addAdvisors(advisors);
  proxyFactory.setTargetSource(targetSource);
  customizeProxyFactory(proxyFactory);

  proxyFactory.setFrozen(this.freezeProxy);
  if (advisorsPreFiltered()) {
    proxyFactory.setPreFiltered(true);
  }

  // Use original ClassLoader if bean class not locally loaded in overriding class loader
  ClassLoader classLoader = getProxyClassLoader();
  if (classLoader instanceof SmartClassLoader smartClassLoader && classLoader != beanClass.getClassLoader()) {
    classLoader = smartClassLoader.getOriginalClassLoader();
  }
  return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));
}

6.5 总结

  1. 通过注解@EnableAspectJAutoProxy引入AnnotationAwareAspectJAutoProxyCreator,这是一个Bean后处理器,主要关注创建对象后;
  2. 容器创建Bean后,查找是否有符合条件的Advisor,如果有,则需要创建代理对象;
    1. 查找符合条件的Advisor,涉及解析@Aspect注解,将高级切面转换为低级切面;
    2. 低级切面又包括AdvicePointCut
  3. 创建代理对象,直接使用ProxyFactory进行创建;

7. 其他

除了上面的Spring AOP 知识,还可以关注以下知识:

7.1 AspectJ表达式

https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/pointcuts.html

7.2 代理原理

JDK动态代理原理和CGLIB代理原理

7.3 AspectJ

使用AspectJ实现AOP,案例。

7.4 自己实现AOP

结合以上原理,自己实现一个AOP。