AOP流程及原理
目录
一、AOP结构介绍
@Pointcut
通知
原理
连接点
拦截器
二、Bean介入点
EnableAspectJAutoProxy
AspectJAutoProxyRegistrar
AnnotationAwareAspectJAutoProxyCreator
AbstractAutoProxyCreator
实例前执行
初始化后执行
循环依赖会调用
总结
三、处理切面
获取所有切面其下通知方法
获取切面
获取切面下的通知方法
通知方法的封装
通知方法与Bean匹配
总结
四、创建代理对象
五、代理执行方法
六、总结
一、AOP结构介绍
我们先看个简单的AOP例子:
结果:
我们来细数一下有哪些要素 ?
@Aspect:切面类,告诉Spring我这个类是个切面,里面有特殊处理方法
@Pointcut:切点,告诉Spring我要针对那些业务方法进行增强
@Before、@Around、@AfterReturning、@After、@AfterThrowing:通知,告诉Spring针对后要做什么处理
要素就这些吧,@Aspect就不说了就是个标识,主要是切点和处理方法吧
@Pointcut
这个注解值的格式是:表达标签 (表达式格式),用白话说就是用了一种表达式来代表我要针对什么来进行特殊处理
execution:用于匹配方法执行的连接点
within:用于匹配指定类型内的方法执行
this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配
args:用于匹配当前执行的方法传入的参数为指定类型的执行方法
@within:用于匹配所以持有指定注解类型内的方法
@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
@args:用于匹配当前执行的方法传入的参数持有指定注解的执行
@annotation:用于匹配当前执行方法持有指定注解的方法
bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法
通知
我们上述看到了有五种通知注解,分别表示如下,表示有五种特殊处理方式:
@Before: 前置通知,在目标方法执行前执行
@Around: 环绕通知,可以在目标方法前、后进行处理,还可以修改目标方法返回值
@AfterReturning: 后置通知,在目标方法后执行(发生异常便不会执行)
@After: 最终通知,不管异常还是正常一定都会执行
@AfterThrowing:异常通知,在目标方法发生异常后执行
原理
一提起AOP可能第一反应就是动态代理,但是真的就只有动态代理这么简单吗?我们看一个动态代理的例子(以JDK动态代理为例):
这乍一看好像就是这个道理啊,好像全满足了呀,真满足吗?环绕通知要怎么做?通知有多个,有多个处理方法怎么做?总不可能一直往这里面塞吧,还有环绕通知需要在invoke方法外面再套一层吧,有多个的话无限套娃?
那要怎么做?注意看这是不是都是串行执行的,串行执行的拦截处理方法是什么?拦截器链!!
流程如下图所示:
注意看所有通知都是多个:
无环绕,无异常的情况下:所有前置通知 --> 目标方法 --> 所有后置通知 --> 所有最终通知 --> 返回
无环绕,有异常的情况下:所有前置通知 --> 目标方法 --> 所有异常通知 --> 所有最终通知 --> 返回(这里注意前置、目标、后置任何一个异常都会到异常通知)
有环绕的情况下:先执行环绕前置 --> 再执行链条 --> 然后环绕后置(如下图)
多个环绕会怎样?注意环绕通知本身就是链条的里面的,只不过在最前面执行,多个环绕就会像这样:
好了重点来了,我们知道原理了动态代理+拦截器链,我们需要知道Spring怎么帮我们组装的?
动态代理很简单就两种方式:JDK 和 Cglib
拦截器链:是不是需要把上述切面里面的方法全提取出来封装好,然后最后组装成链条
连接点:拦截器通过什么连接到一起?需要相同的连接点吧,所谓连接点是指那些被拦截到的方法
连接点
在Spring里面连接点是Joinpoint这个接口:
如上图可见就两个实现类:
ReflectiveMethodInvocation:提供给JDK动态代理方式使用
CglibMethodInvocation:提供给Cglib动态代理方式使用
二、拦截器
既然知道是拦截器链了,那每个通知方法应该都有对应的拦截器,我们去看看(只看invoke方法哈):
1、前置通知拦截器 MethodBeforeAdviceInterceptor:
2、后置通知拦截器AfterReturningAdviceInterceptor:
3、异常通知拦截器ThrowsAdviceInterceptor :
4、最终通知AspectJAfterAdvice :
5、环绕通知AspectJAroundAdvice :
以上就是关于通知链条里面所有最后会执行的方法,可以看到共同点就是invoke方法的传参MethodInvocation ,这不就是我们之前说的连接点嘛,
当然还有很多内置的其他拦截器,但这都跟我们AOP拦截器没关系
以上基础概念相信大家都懂了,接下来我们看看Spring是怎么代理一个Bean的,是怎么为这个Bean组装这些拦截器的
三、Bean介入点
这AOP代理到底是在Bean生成流程中哪个地方介入进来为我们生成代理对象的 ?
从AOP配置加载点一看便知,开启AOP的配置注解是 @EnableAspectJAutoProxy(现在已经默认开启了,不需要加注解也行,配置类是AopAutoConfiguration)
1、EnableAspectJAutoProxy
@EnableAspectJAutoProxy注解内部导入了一个类AspectJAutoProxyRegistrar
2、AspectJAutoProxyRegistrar
这个类实现了ImportBeanDefinitionRegistrar接口,这个接口之前说过了,可以注册bean的定义信息BeanDefination,所以我们要看看注册的这个是什么?干了什么?
沿着那个方法一路往下,发现注册了AnnotationAwareAspectJAutoProxyCreator
3、AnnotationAwareAspectJAutoProxyCreator
这个类可谓是最重要的类了,从下方的类图上看,它实现了很多接口,还有我们非常熟悉的后置处理器,在这里面主要实现了4个方法:
setBeanFactory:实例化后,初始化前调用
getEarlyBeanReference:和三级缓存有关,存在循环依赖里面会调用
postProcessBeforeInstantiation:实例化前执行
postProcessAfterInitialization:初始化后执行
别看有4个方法,其实下面三个方法内部都会调用一样的方法,只是需要注意在Bean生成流程中的介入点
4、AbstractAutoProxyCreator
在目标对象创建之前,执行 AbstractAutowireCapableBeanFactory#Object bean = resolveBeforeInstantiation(beanName, mbdToUse) --> applyBeanPostProcessorsBeforeInstantiation(targetType, beanName) ---> Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName),主要是判断代理目标对象是否已经存在,存在了且需要代理就走getAdvicesAndAdvisorsForBean方法,然后调用createProxy()方法创建代理对象
5、目标对象创建完且初始化后执行
目标对象创建完且初始化后执行,AbstractAutowireCapableBeanFactory#initializeBean --> wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)
--> result = beanProcessor.postProcessAfterInitialization(result, beanName),会调用wrapIfNecessary()方法
wrapIfNecessary()方法也会调用 getAdvicesAndAdvisorsForBean方法来获取对应的通知处理,如果没获取到通知处理方法说明不需要代理,获取到了就要创建代理对象了createProxy()
注意: 这里的通知处理就是切面里面的通知方法,getAdvicesAndAdvisorsForBean就是获取所有的切面类里面的切点与Bean来匹配,匹配上了说明这个Bean要被代理,同时会封装匹配的切点对应的所有通知返回
6、循环依赖会调用
getEarlyBeanReference,三级缓存,存在循环依赖则会调用,这里put进去代表已经生成代理了,所以后续初始化后调用的时候会get判断一次,也会调用wrapIfNecessary()方法
7、总结
所以会在Bean实例化前、循环依赖、初始化后介入处理,当然只会处理一次,最终都会调用getAdvicesAndAdvisorsForBean方法来对Bean进行切点匹配,匹配上了就调用createProxy方法生成代理对象然后返回
四、处理切面
AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean()作用:
会先获取所有的切面其下的通知方法(@Before、@Around、@AfterReturning、@After等),然后根据切点表达式去和这个Bean对象匹配, 将匹配成功的通知方法返回,
这就说明该Bean需要被代理,匹配成功的通知方法排序后就是需要执行的方法调用链
1、获取所有切面其下通知方法
使用了模板方法模式,获取切面,AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors 有个父类的方法是获取一些实现了Advisor接口的Bean,我们重点关注被@Aspect注解标识的Bean的处理
BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors
会遍历所有的Bean找到其中被注解@Aspect标识的,然后去处理其下的切点和通知方法
2、获取@Aspect切面下的通知方法
ReflectiveAspectJAdvisorFactory.getAdvisors
遍历切面下的所有方法,去找方法上是否有相应的注解,如果有则需要封装处理
ReflectiveAspectJAdvisorFactory.getAdvisor
遍历我需要的注解,在方法上找注解是否存在,存在的就需要封装处理
3、通知方法的封装
InstantiationModelAwarePointcutAdvisorImpl
这个在构造里面就会对通知方法进行处理封装
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
validate(candidateAspectClass);
AspectJAnnotation<?> aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
if (aspectJAnnotation == null) {
return null;
}
// If we get here, we know we have an AspectJ method.
// Check that it's an AspectJ-annotated class
if (!isAspect(candidateAspectClass)) {
throw new AopConfigException("Advice must be declared inside an aspect type: " +
"Offending method '" + candidateAdviceMethod + "' in class [" +
candidateAspectClass.getName() + "]");
}
if (logger.isDebugEnabled()) {
logger.debug("Found AspectJ method: " + candidateAdviceMethod);
}
AbstractAspectJAdvice springAdvice;
switch (aspectJAnnotation.getAnnotationType()) {
case AtBefore:
springAdvice = new AspectJMethodBeforeAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtAfter:
springAdvice = new AspectJAfterAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtAfterReturning:
springAdvice = new AspectJAfterReturningAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterReturningAnnotation.returning())) {
springAdvice.setReturningName(afterReturningAnnotation.returning());
}
break;
case AtAfterThrowing:
springAdvice = new AspectJAfterThrowingAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
}
break;
case AtAround:
springAdvice = new AspectJAroundAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtPointcut:
if (logger.isDebugEnabled()) {
logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
}
return null;
default:
throw new UnsupportedOperationException(
"Unsupported advice type on method: " + candidateAdviceMethod);
}
// Now to configure the advice...
springAdvice.setAspectName(aspectName);
springAdvice.setDeclarationOrder(declarationOrder);
String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
if (argNames != null) {
springAdvice.setArgumentNamesFromStringArray(argNames);
}
springAdvice.calculateArgumentBindings();
return springAdvice;
}
4、通知方法与 Bean匹配
AbstractAdvisorAutoProxyCreator.findAdvisorsThatCanApply
5、总结
所以这一步会找到所有的切面,遍历其下的所有切点和通知方法,然后根据切点中的表达式去与Bean对象匹配,获取所有匹配成功的通知方法,将这些通知方法排序后就是最后的方法执行链,
同时也说明该Bean需要被代理,所以需要创建代理对象
五、创建代理对象
AbstractAutoProxyCreator.createProxy
这里实际就是在创建代理对象前填充一下必要信息,然后创建代理对象,默认是采用JDK动态代理,如果被代理的目标对象不是接口,则会采用Cglib动态代理
CglibAopProxy:Cglib动态代理逻辑类
JdkDynamicAopProxy:Jdk动态代理逻辑类(我们以这个为例)
JdkDynamicAopProxy.getProxy
这一步很简单就是直接创建代理对象,处理类是this,说明该类本身就是处理类
六、代理执行方法
我们以JDK动态代理为例,最终代理对象在执行方法的时候就会调用该方法:
JdkDynamicAopProxy.invoke
六、总结
1、AOP代理对象的生成机会是在Bean实例化前、初始化后这两个位置判断生成的(以初始化后为主,其他阶段属于特殊阶段)
2、通过获取所有的切面下的通知方法以切点表达式来与Bean匹配,来判断该Bean是否需要被代理,同时准备好了与该Bean相关的所有增强方法
3、AOP默认采用JDK动态代理的方式,如果被代理目标对象不是接口,则会采用Cglib的代理方法
4、AOP的底层原理虽然是动态代理,但是我觉得最重要的还是执行的方法调用链非常巧妙
5、在逻辑实现上:每种通知在调用链上执行的方式及其执行顺序决定了其扮演的角色
6、每个通知最后执行类在前面已经给出,可直接查看学习
最后附上个执行结构图
AOP流程及原理的更多相关文章
- Spring aop的实现原理
简介 前段时间写的java设计模式--代理模式,最近在看Spring Aop的时候,觉得于代理模式应该有密切的联系,于是决定了解下Spring Aop的实现原理. 说起AOP就不得不说下OOP了,OO ...
- AOP切面实现原理以及多个切面切同一个地方时的优先级讲解
此博文的编写,源于前段时间的惨痛面试经历.刚好近几天尘埃落定.手头事少,遂总结一二,与各位道友分享,欢迎吐槽指正.今年年初的这段面试经历,已于之前的博文中 整理发出(https://www.cnblo ...
- Struts框架核心工作流程与原理
1.Struts2架构图 这是Struts2官方站点提供的Struts 2 的整体结构. 执行流程图 2.Struts2部分类介绍 这部分从Struts2参考文档中翻译就可以了. ActionM ...
- Atitit 分区后的查询 mysql分区记录的流程与原理
Atitit 分区后的查询 mysql分区记录的流程与原理 1.1.1. ibd是MySQL数据文件.索引文件1 1.2. 已经又数据了,如何分区? 给已有的表加上分区 ]1 1.3. 分成4个区, ...
- Spring AOP异常捕获原理
Spring AOP异常捕获原理: 被拦截的方法,须显式的抛出异常,且不能做任何处理, 这样AOP才能捕获到方法中的异常,进而进行回滚. 换句话说,就是在Service层的 ...
- 漫画 | Spring AOP的底层原理是什么?
1.Spring中配置的bean是在什么时候实例化的? 2.描述一下Spring中的IOC.AOP和DI IOC和AOP是Spring的两大核心思想 3.谈谈IOC.AOP和DI在项目开发中的应用场景 ...
- 支付宝app支付java后台流程、原理分析(含nei wang chuan tou)
java版支付宝app支付流程及原理分析 本实例是基于springmvc框架编写 一.流程步骤 1.执行流程 当手机端app(就是你公司开发的app)在支付 ...
- 关于spring,IOC和AOP的解析原理和举例
引用自:https://blog.csdn.net/paincupid/article/details/43152397 IOC:就是DAO接口的实现不再是业务逻辑层调用工厂类去获取,而是通过容器(比 ...
- 《CI/CD 流程以及原理说明》
自动化部署 CI/CD 是一种通过在应用开发阶段引入自动化来频繁向客户交付应用的方法.CI/CD 的核心概念是持续集成.持续交付和持续部署.作为一个面向开发和运营团队的解决方案,CI/CD 主要针对在 ...
- Spring AOP 的实现 原理
反射实现 AOP 动态代理模式实例说明(Spring AOP 的实现 原理) 比如说,我们现在要开发的一个应用里面有很多的业务方法,但是,我们现在要对这个方法的执行做全面监控,或部分监控.也许我们 ...
随机推荐
- Eclipse中的快捷键:批量修改指定的变量名、方法名、类名等:alt + shift + r
/* * Eclipse中的快捷键: * 1.补全代码的声明:alt + / * 2.快速修复: ctrl + 1 * 3.批量导包:ctrl + shift + o * 4.使用单行注释:ctrl ...
- LeetCode必刷100题:一份来自面试官的算法地图(题解持续更新中)
大家好,我是忍者程序员.上一篇文章我们讨论了如何科学地刷题,今天我要带大家深入了解这100道精选题目背后的分类逻辑.作为一名面试官,我希望通过这篇文章,为大家绘制一张完整的算法知识地图. 为什么要按类 ...
- 效率起飞!天翼云并行文件服务HPFS高效应对AI时代大模型训练存储挑战!
国内外AI大模型层出不穷,训练数据复杂程度更是呈指数级增加.如今,在万亿级参数时代,单个资源池已无法满足大模型训练场景中动辄PB级的数据存储量,对于企业来说,启用多个资源池构成的分布式存储势在必行. ...
- 鸿蒙页面开发 - 组件复用样式 @Styles
这篇文章介绍一个装饰器 @Styles 他的主要作用是: 当多个组件都有相同的样式,如果每个组件单独设置,会造成大量重复的代码冗余.这时我们可以使用 @Styles 将这些相同样式封装成一个方法,供这 ...
- 探寻SRC漏洞平台
探寻SRC漏洞平台 SRC(Security Researcher Acknowledgement Program)是各大互联网厂商开启的漏洞发现奖励计划,也就是我们常说的漏洞赏金计划(bug bou ...
- 我来告诉你怎么在macOS上畅玩金铲铲之战
天选福星,灵蛇献瑞,<金铲铲之战>"天选福星"赛季好运上线!请接收这份来自<金铲铲之战>的新春邀约--"天选福星"正式回归,羁绊焕新升级 ...
- server_patrol.sh服务器巡查脚本
server_patrol.sh #!/bin/bash #!/usr/bin/expect -f#! auther by wangxp #定义一个变量 LANG="zh_CN.UTF-8& ...
- 动态编译 Java 的神器 Liquor v1.3.10 发布
Liquor 是一个开源的轻量级 Java 动态编译器(零依赖,40KB),它可以在运行时编译 Java 字符串代码片段.类.方法等. 源码地址:https://gitee.com/noear/liq ...
- 从android中删除短信
代码如下: getContentResolver().delete(Uri.parse("content://sms/#"),"address=?", new ...
- DOS使用技巧整理 [典型案例分享]
最早于2008年系统地学习MS-Dos,当时刚初中毕业.从小深受父亲影响,对电脑的技术比较感兴趣,特别是对DOS.随着学习得不断深入,接触了大量数字媒体软件.框架还有编程界的知识,发现DOS有不可磨灭 ...