【Spring源码分析】Spring之AOP底层源码解析和@Async源码解析

阅读此需阅读下面这些博客先
【Spring源码分析】Bean的元数据和一些Spring的工具
【Spring源码分析】BeanFactory系列接口解读
【Spring源码分析】执行流程之非懒加载单例Bean的实例化逻辑
【Spring源码分析】从源码角度去熟悉依赖注入(一)
【Spring源码分析】从源码角度去熟悉依赖注入(二)
【Spring源码分析】@Resource注入的源码解析
【Spring源码分析】循环依赖的底层源码剖析
【Spring源码分析】Spring的启动流程源码解析
【Spring源码分析】解析配置类-ConfigurationClassPostProcessor源码分析

一、预备知识

在看源码前需要点前置知识,不然源码也很难进行下去。

第一点:
首先是需要知道 Spring AOP 是对面向切面编程这种思想的一种实现,而这种实现其他相关技术也是可以支持的,而Spring AOP中注解(Pointcut、Before、Around…)是直接拿 AspectJ 来用的,也仅仅是拿来用,但是Spring启动时候对这些注解是解析,都是Spring自己去做的,这也是 Spring 和 AspectJ 的关系,只是拿它规范的注解来用而已。

第二点:
AOP的概念:

  1. Aspect(Advisor):表示切面,就比如@Aspect注解修饰的类就是切面,或者说实现了Advisor接口;
  2. Join Point:表示连接点,表示一个程序在执行过程中的一个点,比如一个方法的执行;
  3. Advice:表示通知,表示在一个特定连接点上所采取的动作;
  4. Pointcut:表示切点,用来匹配多个连接点的;
  5. Target Object:目标对象,被代理的对象;
  6. AOP Proxy:表示代理工厂,用来创建代理对象的,在Spring Framework 中,要么是 JDK 动态代理,要么是 CGLIB 代理

第三:
针对上面的通知 Advice,表示代理逻辑,一般有以下注解修饰,然后被解析成对应的Advice类:

  1. @Before:AspectJMethodBeforeAdvice,实际上就是 MethodBeforeAdvice;
  2. @AfterReturning:AspectJAfterReturningAdvice,实际上就是一个AfterReturningAdvice
  3. @AfterThrowing:AspectJAfterThrowingAdvice,实际上就是一个MethodInterceptor
  4. @After:AspectJAfterAdvice,实际上就是一个MethodInterceptor
  5. @Around:AspectJAroundAdvice,实际上就是一个MethodInterceptor

二、ProxyFactory

(为什么说它,是因为Spring中的AOP或者所用到了动态代理的逻辑里,如@Async,@Lazy…都是通过 ProxyFactory去获取对应的代理实例的)

  1. 它getProxy的逻辑就是,先判断是使用哪个 AopProxy,JdkDynamicAopProxy 或 ObjenesisCglibAopProxy
  2. 随后调用对应的 AopProxy#getProxy方法获取对应的代理对象

AopProxy的获取

先解释解释它,再源码分析它
Spring AOP 的实现用了俩种动态代理方式,一个是 JDK 动态代理,一个是 CGLIB 动态代理;这俩种代理如何使用都是由 ProxyFactory 去决定的(就是最后获得的AopProxy是JDK的还是CGLIB的)。源码当中就是通过 ProxyFactory 去生成代理实例的。

使用如下:

	public static void main(String[] args) {
		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.setTarget(new UserService());
		proxyFactory.addAdvisor(new UserServiceAdvisor());
		proxyFactory.setInterfaces(new Class[]{TestService.class});
		UserService proxy = (UserService) proxyFactory.getProxy();
		proxy.aaa();
		}
		// 可以看见使用ProxyFactory需要传递Advisor和Target

原理:

ProxyFactory会给我们去判断,如果实现了接口,那么ProxyFactory底层就会使用 Jdk 动态代理,如果没有实现接口,就会用 CGLIB 技术。(其实按照我们的编码习惯,一般容器的Bean都实现了接口的)
源码如下:

	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		// 如果ProxyFactory的isOptimize为true,Spring认为cglib比jdk动态代理要快
		// 或者isProxyTargetClass为true,
		// 或者被代理对象没有实现接口,
		// 或者只实现了SpringProxy这个接口
		// 那么则利用Cglib进行动态代理,但如果被代理类是接口,或者被代理类已经是进行过JDK动态代理而生成的代理类了则只能进行JDK动态代理

		// 其他情况都会进行JDK动态代理,比如被代理类实现了除SpringProxy接口之外的其他接口

		// 是不是在GraalVM虚拟机上运行
		if (!NativeDetector.inNativeImage() &&
				(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {

			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}

这个是否有接口不是ProxyFactory去解析判断的,是判断我们外面是否通过 addInterface 向 ProxyFactory 中的 interfaces 集合中加了对应的接口。AOP进行代理的时候会进行如下,即是手动加的:

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/8c2c45f5200e44a1b9b1576de409d2e3.pn

  • optimize 默认是 false;
  • proxyTargetClass 也是默认是 false;

就是说默认情况下,只要对应代理类实现了接口,就会使用 JDK 的动态代理,而没有实现接口就会使用CGLIB的动态代理。

对应 AopProxy#getProxy() 的实现

先看 JdkDynamicAopProxy#getProxy() 的逻辑,很简单就是Proxy去new一个代理对象,需要注意的是把this传递进了InvocationHandler参数上,说明JdkDynamicAopProxy实现了InvocationHandler:

	@Override
	public Object getProxy(@Nullable ClassLoader classLoader) {
		// this实现了InvocationHandler
		return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
	}

接下来分析重点:

JdkDynamicAopProxy#invoke()
只分析核心代码:

			// 被代理对象和代理类
			target = targetSource.getTarget();
			Class<?> targetClass = (target != null ? target.getClass() : null);

			// 代理对象在执行某个方法时,根据方法筛选出匹配的Advisor,并适配成Interceptor
			// 获取Advice链
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

			if (chain.isEmpty()) {
				// 如果没有Advice,则直接调用对应方法
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			else {
			// 如果Advice链不为空,就交给ReflectiveMethodInvocation处理
				MethodInvocation invocation =
						new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				retVal = invocation.proceed();
			}
  • 先是获取到代理类和代理实例;
  • 然后去根据方法去筛选出匹配的 Advisor(注意这是invoke的逻辑,是根据Method去找对应的…),并适配成对应的Interceptor,就是Advice;
  • 然后判断chain是否是空,就是判断有没有匹配的 Advisor 的意思,没有就直接执行方法,有就交个 ReflectiveMethodInvocation

获取方法匹配的 Advice 逻辑如下:

  • 先是拿到设置到ProxyFactory中的所有Advisor;
  • 然后拿Advisor中的Pointcut去筛选,先根据classFilter筛选类,然后根据MethodMatcher去筛选方法;
  • 最后封装成 MethodInterceptor 放入集合中进行返回。
  • 就是说上面那集合里的都是MethodInterceptor对象,它是一个函数式接口,里面就一个invoke方法,参数是MethodInvocation,注意和ReflectiveMethodInvocation类型匹配。

然后如果返回的这个匹配到的通知链有的话就会去调用 ReflectiveMethodInvocation#proceed 方法,其源码如下:

	@Override
	@Nullable
	public Object proceed() throws Throwable {

		// We start with an index of -1 and increment early.
		// currentInterceptorIndex初始值为-1,每调用一个interceptor就会加1
		// 当调用完了最后一个interceptor后就会执行被代理方法
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		// currentInterceptorIndex初始值为-1
		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);

		// 当前interceptor是InterceptorAndDynamicMethodMatcher,则先进行匹配,匹配成功后再调用该interceptor
		// 如果没有匹配则递归调用proceed()方法,调用下一个interceptor
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// Evaluate dynamic method matcher here: static part will already have
			// been evaluated and found to match.
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
			// 动态匹配,根据方法参数匹配
			if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				// 不匹配则执行下一个MethodInterceptor
				return proceed();
			}
		}
		else {

			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
			// 直接调用MethodInterceptor,传入this,在内部会再次调用proceed()方法进行递归
			// 比如MethodBeforeAdviceInterceptor
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

有点像责任链模式,但又不完全是,因为是通过了这个proceed进行过渡的。简单画个简图吧:

在这里插入图片描述说白了就是先执行的MethodInterceptor,压栈,然后最后执行被代理的方法,最后慢慢对MethodInterceptor进行出栈,就这么个过程。

AOP 其底层实现逻辑是一样的,只是构造代理对象是使用继承类的方式,而JDK是采用实现接口的方式,CGLIB是基于ASM的,相比之下性能要比JDK要高,但在这解析动态代理这里,其实没有啥本质区别。

三、AOP源码分析

首先呢,一般我们是在配置类上加上@EnableAspectJAutoProxy去开启AOP(看了前面源码就知道,Spring容器的都可以称为配置类)

@EnableAspectJAutoProxy 导入了一个 AspectJAutoProxyRegister

在这里插入图片描述而它就是把 AnnotationAwareAspectJAutoProxyCreator 注册成BeanDefinition,然后在Spring解析完这个配置类后,下一步就是实例化BeanPostProcessor了,就会把这个实例化放入Spring容器中了(在阐述Spring启动流程阐述了)。
在这里插入图片描述就是说咱只要分析 AnnotationAwareAspectJAutoProxyCreator 源码就好了,类关系图如下:
在这里插入图片描述它主要是在Bean的生命周期中的初始化后那个时候会进行AOP的分析。

在这里插入图片描述

  • 先获取容器类的Advisor

    • 容器中实现Advisor接口的;
    • 被@Aspect注解修饰了的,将其@Before、@After、@AfterReturn、@AfterThrowing修饰的方法封装成Advice然后进行返回;
    • 在这里插入图片描述
    • 切点Aspect嘛,Pointcut和Advice组成的;
    • 在这里插入图片描述
  • 然后就是构建一个 ProxyFactory 实例,将这个 Advisor 集合放入进去,最后调用 ProxyFactory#getProxy 获取对应的代理对象,就OK了;

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

四、Async源码解析

我们要使用 @Async 的话需要加 @EnableAsync,
这个注解也会注入一个BeanPostProcessor。
在这里插入图片描述在这里插入图片描述
我找解析地方找了我半天,它是实现了BeanFactoryAware 接口,它是在初始化前去执行对应Aware方法的时候构建了一个Advisor:
在这里插入图片描述通知逻辑不难,就是把这个方法逻辑封装成Callable对象,然后直接doSubmit,是直接用的线程池去执行,配合的CompletableFuture。

在这里插入图片描述在这里插入图片描述但是你看看它的Executor

在这里插入图片描述在这里插入图片描述那一直创建线程,不就浪费吗,所以使用@Async的时候记得自己定义一个Executor到容器中。

这个构造的Pointcut匹配也比较简单,就是判断方法上有没有@Async注解的。

那初始化后方法实现更简单:

在这里插入图片描述

五、总结

  • 使用 @EnableAspectJAutoProxy 开启SpringAOP;

  • Spring AOP 本质是通过动态代理去实现的,它内部是使用的 ProxyFactory 去获取的代理对象,ProxyFactory 支持 JDK 和 CGLIB 动态代理,Spring AOP 在实例化 ProxyFactory 的时候,其选择逻辑主要和 optimize、proxyTargetClass、是否向ProxyFactory填写了这个类实现的接口,有的话为false,但三个参数都空false时,就会选择JDK动态代理,反之去实现CGLIB动态代理。optimize、proxyTargetClass 默认都是 false。Spring在创建ProxyFactory的时候涉及到如下参数变化:
    在这里插入图片描述就是只有当实现的接口有方法的时候才会往proxyFactory中添加接口,也就是走JDK动态代理,如果没的话就把ProxyTargetClass调成true,表示走CGLIB动态代理。

如果我们所有的代理对象都使用CGLIB动态代理,那我们配置时:@EnableAspectJAutoProxy(proxyTargetClass = true)即可。(CGLIB加载慢但是执行快,JDK的话加载快,但是执行慢)如果可以的话还是建议把 proxyTargetClass 调整为 true,一是执行方法快,二是不会出现类型转换错误风险。

  • 当代理对象执行方法的时候,首先是去找到ProxyFactory中符合的Advisor,通过里面的Pointcut去筛选,然后将里面的Advice封装成MethodInterceptor对象,最后行成一个 MethodInterceptor 链;
  • 最后先执行MethodInterceptor链,然后执行对应的方法;
  • 在使用 @EnableAspectJAutoProxy 的时候会往容器中注入一个 AnnotationAwareAspectJAutoProxyCreator ,它会在实例化某Bean的初始化后阶段把符合这个Bean里面方法的Advisor都找到封装起来,然后通过ProxyFactory去得到对应代理对象进行返回。

使用 @EnableAsync 开启 @Async 注解的解析;

  • 使用 @EnableAsync 注解会向容器中注入一个 AsyncAnnotationBeanPostProcessor,这个实现了 BeanFactoryAware 接口;
  • 那么在初始化前的时候会执行BeanFactoryAware这个实现的方法,在这个方法里,会实例化一个 Advisor 赋值给属性,由对应Pointcut和Advice组成,Pointcut就是去匹配@Async注解,Advice就是使用Executor去执行那个方法;
  • 而在真正初始化后,会去拿到这个Advisor去判断当前实例化的Bean中有无符合Pointcut的方法,有封装成对应的Advisor然后返回通过ProxyFactory返回一个代理对象。
  • 这里需要注意的就是,Advice里使用的Executor如果容器里没有TaskExecutor类型的Bean的话,Advice使用的是一个SimpleAsyncTaskExecutor,它是新new一个线程去执行的,这样会造成资源的浪费,所以想使用@Async的话,记得向容器里放一个Executor的Bean
  • 11
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
- chapter1:[基本项目构建(可作为工程脚手架),引入web模块,完成一个简单的RESTful API](http://blog.didispace.com/spring-boot-learning-1/) - [使用Intellij中的Spring Initializr来快速构建Spring Boot/Cloud工程](http://blog.didispace.com/spring-initializr-in-intellij/) ### 工程配置 - chapter2-1-1:[配置文件详解:自定义属性、随机数、多环境配置等](http://blog.didispace.com/springbootproperties/) ### Web开发 - chapter3-1-1:[构建一个较为复杂的RESTful API以及单元测试](http://blog.didispace.com/springbootrestfulapi/) - chapter3-1-2:[使用Thymeleaf模板引擎渲染web视图](http://blog.didispace.com/springbootweb/) - chapter3-1-3:[使用Freemarker模板引擎渲染web视图](http://blog.didispace.com/springbootweb/) - chapter3-1-4:[使用Velocity模板引擎渲染web视图](http://blog.didispace.com/springbootweb/) - chapter3-1-5:[使用Swagger2构建RESTful API](http://blog.didispace.com/springbootswagger2/) - chapter3-1-6:[统一异常处理](http://blog.didispace.com/springbootexception/) ### 数据访问 - chapter3-2-1:[使用JdbcTemplate](http://blog.didispace.com/springbootdata1/) - chapter3-2-2:[使用Spring-data-jpa简化数据访问层(推荐)](http://blog.didispace.com/springbootdata2/) - chapter3-2-3:[多数据源配置(一):JdbcTemplate](http://blog.didispace.com/springbootmultidatasource/) - chapter3-2-4:[多数据源配置(二):Spring-data-jpa](http://blog.didispace.com/springbootmultidatasource/) - chapter3-2-5:[使用NoSQL数据库(一):Redis](http://blog.didispace.com/springbootredis/) - chapter3-2-6:[使用NoSQL数据库(二):MongoDB](http://blog.didispace.com/springbootmongodb/) - chapter3-2-7:[整合MyBatis](http://blog.didispace.com/springbootmybatis/) - chapter3-2-8:[MyBatis注解配置详解](http://blog.didispace.com/mybatisinfo/) ### 事务管理 - chapter3-3-1:[使用事务管理](http://blog.didispace.com/springboottransactional/) - chapter3-3-2:[分布式事务(未完成)] ### 其他内容 - chapter4-1-1:[使用@Scheduled创建定时任务](http://blog.didispace.com/springbootscheduled/) - chapter4-1-2:[使用@Async实现异步调用](http://blog.didispace.com/springbootasync/) #### 日志管理 - chapter4-2-1:[默认日志的配置](http://blog.didispace.com/springbootlog/) - chapter4-2-2:[使用log4j记录日志](http://blog.didispace.com/springbootlog4j/) - chapter4-2-3:[对log4j进行多环境不同日志级别的控制](http://blog

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

假正经的小柴

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

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

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

打赏作者

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

抵扣说明:

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

余额充值