Spring源码系列之AOP底层源码

91 阅读13分钟

前言

经历半个月的时间的学习,这期带来Spring源码之AOP底层源码的实现。这期我们从概念慢慢引入到源码,以及代理的简单应用实现。 如果您觉得博主写的还可以,请点点关注。好了,废话不多说,直奔主题吧。

官方文档

1、AOP相关的概念

1.1、面向切面编程

面向切面编程(Aspect-oriented Programming 简称AOP),是相对面向对象编程(Object-oriented Programming 简称OOP)的框架,作为OOP的一种功能补充,OOP主要的模块单元是类,而AOP是切面(Aspect)。切面会将诸如事务管理这样跨越多个类型和对象的关注点模块化(在AOP的语义中,这类关注点被称为横切关注点)。

AOP是Spring框架重要的组件,虽然SpringIOC容器没有依赖AOP,但AOP提供了强大的功能,用做对SpringIOC的补充。

AOP在Spring Framework中用于:

  • 提供声明式企业服务,特别是用于替代EJB的声明式服务。最重要的服务是声明式事务管理,这个服务建立在Spring的抽象事物管理之上。
  • 允许开发者实现自定义切面,使用AOP来完善OOP的功能。

1.2、AOP概念

  • 切面(Aspect):指关注点模块化,这个关注点可能会横切多个对象。在Spring AOP中,切面可以使用通用类基于模式的方式或者在普通类中以@Aspect 注解来实现。
  • 连接点(Join point):在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在Spring AOP中,一个连接点总是代表一个方法的执行。
  • 通知(Advice):在切面的某个特定的连接点上执行的动作。通知类型包括(Around、Before、After、AfterReturning、AfterThrowing)
  • 切点(Pointcut):匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行。Spring默认使用AspectJ切点语义。
  • 引入(Introduction):声明额外的方法或者某个类型的字段。Spring允许引入新的接口到任何被通知的对象上。
  • 目标对象(Target Object):被一个或者多个切面所通知的对象。也被称作被通知对象。==SpringAOP是通过运行时代理实现的,那么这个对象永远是一个被代理的对象。==
  • AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。
  • 织入(Weaving):把切面连接到其他的应用程序类型或者对象上,并创建一个被通知的对象的过程。这个过程可以在编译时、类加载时、运行时中完成的。 ==Spring和其他纯Java AOP框架一样,是在运行时完成织入的。==

1.3、AOP通知类型

  • 环绕通知(Around Advice):环绕连接点的通知,例如方法调用。这是最强大的一种通知类型,环绕通知可以在方法调用前后完成自定义的行为。
  • 前置通知(Before Advice):在连接点之前运行但无法阻止执行流程进入连接点的通知。
  • 后置通知(After Advice):当连接点退出的时候执行的通知。
  • 后置返回通知(After returning Advice): 在连接点正常完成后执行的通知。
  • 后置异常通知(After throwing Advice):在方法抛出异常退出时执行的通知。 上面的顺序记住,等我们进入到源码的时候,你会看到Spring5.2以及之前版本,在责任链调用的时候是反过来的调用的,在Spring5.3以及之后版本,在责任链调用的时候,就是上面的顺序。

1.4、AOP代理

Spring的代理方式有两种,一种是 JDK动态代理,一种是CGLIB代理。其中JDK动态代理是Spring的默认。 以下两种方式是使用CGLIB代理 ①@EnableAspectJAutoProxy(proxyTargetClass = true) 强制要求Spring使用CGLIB代理。 ②被代理的类不是接口的实现类。

2、AOP代码实现

2.1、声明一个切面类

package com.xu.spring.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * @Author: xuwsh
 * @Date: 2023/08/18/20:07
 * @Description:
 */

@Aspect
@Component
@Order
public class TestAspect {
    @Pointcut("execution(* com.xu.spring.aop.TestCalculate.*(..))")
    public void pointCut() {
    }

//    /**
//     * 环绕通知
//     *
//     * @param joinPoint
//     */
//    @Around("pointCut()")
//    public Object methodAround(JoinPoint joinPoint) {
//        String methodName = joinPoint.getSignature().getName();
//        System.out.printf("执行目标方法【%s】的环绕通知,入参是--->%s%n", methodName, Arrays.asList(joinPoint.getArgs()));
//        return 123;
//    }
    /**
     * 前置通知
     *
     * @param joinPoint
     */
    @Before(value = "pointCut()")
    public void methodBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.printf("执行目标方法【%s】的前置通知,入参是--->%s%n", methodName, Arrays.asList(joinPoint.getArgs()));
        //throw  new SecurityException("前置通知异常");
    }

    /**
     * 后置通知
     *
     * @param joinPoint
     */
    @After(value = "pointCut()")
    public void methodAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.printf("执行目标方法【%s】的后置通知,入参是--->%s%n", methodName, Arrays.asList(joinPoint.getArgs()));
    }

    /**
     * 后置返回通知
     *
     * @param joinPoint
     * @param resultObj
     */
    @AfterReturning(value = "pointCut()", returning = "resultObj")
    public void methodAfterReturning(JoinPoint joinPoint, Object resultObj) {
        String methodName = joinPoint.getSignature().getName();
        System.out.printf("执行目标方法【%s】的后置返回通知,入参是--->%s,后置通知返回的参数---->%s%n", methodName, Arrays.asList(joinPoint.getArgs()), resultObj);
    }

    /**
     * 异常通知
     *
     * @param joinPoint
     */
    @AfterThrowing(value = "pointCut()")
    public void methodAfterThrowing(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.printf("执行目标方法【%s】的异常通知,入参是--->%s%n", methodName, Arrays.asList(joinPoint.getArgs()));
    }
}

2.2、写一个要被代理的类

package com.xu.spring.aop;

import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Component;

/**
 * @Author: xuwsh
 * @Date: 2023/08/18/20:02
 * @Description:
 */
@Component
public class TestCalculate implements Calculate {
    @Override
    public int add(int numA, int numB) {
        System.out.println("执行目标方法:add");
        return numA + numB;
    }

    @Override
    public int sub(int numA, int numB) {
        System.out.println("执行目标方法:sub");
        return numA - numB;
    }

    @Override
    public int div(int numA, int numB) {
        System.out.println("执行目标方法:div");
        return numA / numB;
    }

    @Override
    public int multi(int numA, int numB) {
        System.out.println("执行目标方法:multi");
        return numA * numB;
    }

    @Override
    public int mod(int numA, int numB) {
        System.out.println("执行目标方法:mod");
        Calculate calculate = (Calculate) AopContext.currentProxy();
        int add = calculate.add(numA, numB);
        return add;
    }
}

2.3、声明一个配置类

在配置类中,引用了 @EnableAspectJAutoProxy ,将这个类交给SpringIOC去给我创建一个自动代理的创建器 AnnotationAwareAspectJAutoProxyCreator.Class

package com.xu.spring.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @Author: xuwsh
 * @Date: 2023/07/05/20:27
 * @Description:
 */
// @Configuration
@ComponentScan(basePackages = {"com.xu.spring.aop"})
//@Conditional(value = Order.class)
//@EnableAspectJAutoProxy(proxyTargetClass = true)
@EnableAspectJAutoProxy
//@Import({ProxyTest.class})
public class MainConfig {
}

2.4、运行结果

public class AopTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        Calculate testCalculate = (Calculate) context.getBean("testCalculate");
        int add = testCalculate.div(12, 1);
        System.out.println(add);
    }
}
执行目标方法【div】的前置通知,入参是--->[12, 1]
执行目标方法:div
执行目标方法【div】的后置通知,入参是--->[12, 1]
执行目标方法【div】的后置返回通知,入参是--->[12, 1],后置通知返回的参数---->12
12

2.5、结果分析

我们可以看到,输出的结果为 前置通知---->目标方法----->后置通知---->后置返回通知(后置异常通知) 由此,我们可以看到,前置通知执行代码会在真正方法执行之前先调用,后置通知会在真正方法执行之后在调用,后置返回通知,会在后置通知执行完再调用,如果后置通知有异常,则调用后置异常通知。

3、AOP底层源码分析

首先,在看源码之前,我们首先要知道一件事情,AOP在什么时候生成。 那我们先来看下Spring中,Bean的生命周期。 在这里插入图片描述 其实,AOP有两个地方生成,第一种就是在实例化前的时候去创建动态代理。第二种就是在初始化后去创建动态代理。正常Bean都是在初始化后 去创建动态代理。如果循环依赖存在AOP,则会在实例化前去创建动态代理。第一种属于特殊情况,我们在这里就不去讨论了。 那我们直接去初始化后去看源码。。。。。

3.1、源码

如果源码不知道怎么找的话,看我上一篇博客浅谈SpringIOC

3.1.1、AOP代理对象的创建过程

在这里插入图片描述 在这里插入图片描述

wrapIfNecessary这个方法就是创建代理的主要方法

在这里插入图片描述

isInfrastructureClass这个方法里,主要是检测你这个Bean是不是一个切面Bean,比如我们TestAspect, shouldSkip这个方法是找到Bean里是否存在Advice,将Advice找出来并且进行排序

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

将我的TestAspect类中的所有的AOP通知类型找到,并且按照这个Around--->Before--->After--->AfterReturning---->AfterThrowing 进行排序 排序之后的结果如下:

在这里插入图片描述

在创建执行createProxy()方法之前,我们看下这个方法,这个方法主要做的就是将再次找到Bean中有Advise的注解,然后进行排序。我们看下排序后的结果是什么

在这里插入图片描述 在这里插入图片描述

看到这里你是不是很懵,我们只有四个通知类型,这里怎么会有5个呢,第一个我们可以忽略这个是Spring给我们加的,这里我们不需要关注,我们主要关注排完序的结果,我们之前说的排序是不是Around--->Before--->After-->AfterReturning---->AfterThrowing,然而执行这里之后,我们的排序变成了 AfterThrowing--->AfterReturning--->After---->Before。

接下来,我给你们看下Spring5.3版本排完序的结果

在这里插入图片描述

在Spring5.3版本中,排完序的结果是我们之前排序的顺序Before--->After--->AfterReturning---->AfterThrowing。 ==这里等们看下AOP调用的的时候,我们再来解释一下 ,为什么5.2版本排完序是倒过来的,5.3版本,没有倒过来==

创建动态代理方法 createProxy()

在这里插入图片描述 在这里插入图片描述

我们可以看到 getProxy有两个实现类,一个是CglibAopProxy,一个JdkDynamicAopProxy

在这里插入图片描述

这个方法就是来选择使用那种动态代理方式。 条件判断解析

  • config.ifOptimize():这个默认是flase ,如果你不去手动去修改,那么这个就可以默认忽略
  • config,isProxyTargetClass:这个就是我前面说的,强制使用CGLIB代理的条件判断,就是将@EnableAspectJAutoProxy(proxyTargetClass = true),博主已经试过了,这个就不在这里体现了。
  • hasNoUserSuppliedProxyInterfaces(config):这个判断就是被代理的类是否有接口的实现。如果有接口实现则为false,没有接口实现则给true,显然我这里的TestAspect使用了接口。如果为true则使用CGLIB代理。

在这里插入图片描述

到这里之后,我们就知道什么情况下使用CGLIB代理,什么情况下使用JDK代理。 Spring默认采用JDK代理。如果有兴趣的同学,可以去研究下CGLIB和JDK代理有什么区别。 那么我这里毫无疑问走的JDK代理

在这里插入图片描述

到这里之后,我们就开始创建代理对象了。

在这里插入图片描述

到这里之后,我们的AOP的代理对象就已经创建好了。接下来,我们就要看看AOP是如何调用的。

3.1.2、AOP代理调用

在我们JDK代理的类中,实现了InvocationHandler里的invoke方法 在这里插入图片描述

我们直接来到invoke方法里的这个方法getInterceptorsAndDynamicInterceptionAdvice() 这个方法主要是将我们之前Advise里的通知类型方法,全部转换成统一方法类型,为了方便调用,这里AOP的调用方式采用的是责任链调用方式。

在这里插入图片描述

到下面之后,就开始真正的AOP调用,我们可以看到interceptorsAndDynamicMethodMatchers的数组里有五个方法,currentInterceptorIndex默认是-1,这里使用了++方式,所以在这里,我们先获取到数组下标为0的开始调用下面的,invoke方法

在这里插入图片描述

我们看下invoke的实现类,可以看到包含了我们的前置通知,后置通知,后置返回通知,后置异常通知实现类。而这里类都是实现了MethodInterceptor这个接口,也就是上面所说的,为什么将这些方法转换成统一方法的原因,就是方便我们责任链的调用。

在这里插入图片描述

在这里插入图片描述

我们看下这些类调用invoke的具体实现方法。我们按照Spring5.2版本的来看,因为这里责任链调用很容易蒙,所以,我按照AfterThrowing--->AfterReturning--->After---->Before 这个顺序展示代码

这里有一个Spring给我们加的一个方法,别忘了!!!!,非常关键的一个方法

ExposeInvocationInterceptor

public Object invoke(MethodInvocation mi) throws Throwable {
		MethodInvocation oldInvocation = invocation.get();
		invocation.set(mi);
		try {
			return mi.proceed();
		}
		finally {
			invocation.set(oldInvocation);
		}
	}

AspectJAfterThrowingAdvice

public Object invoke(MethodInvocation mi) throws Throwable {
		try {
			return mi.proceed();
		}
		catch (Throwable ex) {
			if (shouldInvokeOnThrowing(ex)) {
				invokeAdviceMethod(getJoinPointMatch(), null, ex);
			}
			throw ex;
		}
	}

AfterReturningAdviceInterceptor

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

AspectJAfterAdvice

public Object invoke(MethodInvocation mi) throws Throwable {
		try {
			return mi.proceed();
		}
		finally {
			invokeAdviceMethod(getJoinPointMatch(), null, null);
		}
	}

MethodBeforeAdviceInterceptor

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

其实,看上面的调用顺序,你把他们的调用顺序改成写你能看懂得Java代码,你就能看到其中的原理,改写代码如下:

 private Object method() {
        //0、ExposeInvocationInterceptor
        MethodInvocation oldInvocation = invocation.get();
        invocation.set(mi);
        try {
            //1、AspectJAfterThrowingAdvice
            try {
                //2、AfterReturningAdviceInterceptor
                Object retVal;
                //3、AspectJAfterAdvice
                try {
                    //4、MethodBeforeAdviceInterceptor
                    retVal = this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
                } finally {
                    invokeAdviceMethod(getJoinPointMatch(), null, null);
                }
                retVal = this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
                return retVal;
            } catch (Throwable ex) {
                if (shouldInvokeOnThrowing(ex)) {
                    invokeAdviceMethod(getJoinPointMatch(), null, ex);
                }
                throw ex;
            }
        } finally {
            invocation.set(oldInvocation);
        }
    }

看到这里之后,我们在说说前面为什么要进行排序,AfterThrowing--->AfterReturning--->After---->Before,无论你是在前置通知或者后置通知出现异常之后,通过catch捕获异常抛出。 测试结果

在这里插入图片描述 在这里插入图片描述 从这里我们能看出,当后置通知和异常通知都报错的情况下,抛出的异常就是异常通知,后置通知的异常将被放弃掉。 由此我们可以得出,只要异常通知报错,无论前置通知、后置通知、后置返回通知报错,都将返回的是异常通知。 优先级为 异常通知>后置通知>前置通知>后置返回通知。

那我们在说说Spring5.3版本没有进行排序的代码逻辑

private Object method1() {
        //0、ExposeInvocationInterceptor
        MethodInvocation oldInvocation = invocation.get();
        invocation.set(mi);
        try {
            //1、MethodBeforeAdviceInterceptor
            Object retVal = this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
            //2、AspectJAfterAdvice
            try {
                //3、AfterReturningAdviceInterceptor
                try {
                    //4、AspectJAfterThrowingAdvice
                    return retVal;
                } catch (Throwable ex) {
                    if (shouldInvokeOnThrowing(ex)) {
                        invokeAdviceMethod(getJoinPointMatch(), null, ex);
                    }
                    throw ex;
                }
                this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
                return retVal;
            } finally {
                invokeAdviceMethod(getJoinPointMatch(), null, null);
            }
        } finally {
            invocation.set(oldInvocation);
        }

    }

执行顺序测试

在这里插入图片描述 在这里插入图片描述 这里我将前置通知抛异常,输出结果直接是前置通知方法的逻辑,其他方法不会执行。

在这里插入图片描述 这里我将前置通知异常关掉,我设置异常通知异常,惊奇发现,异常通知的方法根本就不走。由此可以得出,当我们的前置通知、后置通知、后置返回通知没有出现异常,异常通知是不会执行的。

在这里插入图片描述 这里我将后置返回通知抛出异常,我们发现,我们的前置通知、后置通知、后置返回通知全部执行完后抛出异常,这里也证实了我上面写的逻辑代码。

在这里插入图片描述 这里我又继续增加后置通知抛出异常,可以看出,后置通知异常将后置返回异常吃掉了。 由此我们可以得出异常通知优先级:前置通知>后置通知>后置返回通知, 在这里,只有前置通知抛出异常后,剩下代码不执行,其他抛出异常代码是继续执行的。 但是这里我又提出一个问题,异常通知在整个流程中,是不参与代码运行的。 在这里插入图片描述 我们将目标方法抛出异常,则异常通知就会被执行,否则异常通知不会被执行。

总结: Spring5.2版本。无论目标方法是否出现异常,只要增强方法中出现异常,异常通知就会被执行。 Spring5.3版本。如果目标方法出现异常。则会执行异常通知方法,否则不会执行异常通知方法。

好了,以上整个SpringAOP从创建到调用的整个过程。内容有点多,如果您认真的看过之后,我相信你会对AOP的流程会有一定的理解。

3.2、AOP流程图

在这里插入图片描述 在这里插入图片描述

4、JDK代理编译代码

在这里插入图片描述 我们来看下编译后的代码,很显然为什么JDK代理调用的是invoke方法,放我们在执行目标方法context方法的时候,就会调用invoke方法去执行代理对象的方法。

天晴了,雨停了,点个关注行不行。