目录

一、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流程及原理的更多相关文章

  1. Spring aop的实现原理

    简介 前段时间写的java设计模式--代理模式,最近在看Spring Aop的时候,觉得于代理模式应该有密切的联系,于是决定了解下Spring Aop的实现原理. 说起AOP就不得不说下OOP了,OO ...

  2. AOP切面实现原理以及多个切面切同一个地方时的优先级讲解

    此博文的编写,源于前段时间的惨痛面试经历.刚好近几天尘埃落定.手头事少,遂总结一二,与各位道友分享,欢迎吐槽指正.今年年初的这段面试经历,已于之前的博文中 整理发出(https://www.cnblo ...

  3. Struts框架核心工作流程与原理

    1.Struts2架构图  这是Struts2官方站点提供的Struts 2 的整体结构.  执行流程图 2.Struts2部分类介绍  这部分从Struts2参考文档中翻译就可以了. ActionM ...

  4. Atitit 分区后的查询  mysql分区记录的流程与原理

    Atitit 分区后的查询  mysql分区记录的流程与原理 1.1.1. ibd是MySQL数据文件.索引文件1 1.2. 已经又数据了,如何分区? 给已有的表加上分区 ]1 1.3. 分成4个区, ...

  5. Spring AOP异常捕获原理

    Spring AOP异常捕获原理:        被拦截的方法,须显式的抛出异常,且不能做任何处理, 这样AOP才能捕获到方法中的异常,进而进行回滚.        换句话说,就是在Service层的 ...

  6. 漫画 | Spring AOP的底层原理是什么?

    1.Spring中配置的bean是在什么时候实例化的? 2.描述一下Spring中的IOC.AOP和DI IOC和AOP是Spring的两大核心思想 3.谈谈IOC.AOP和DI在项目开发中的应用场景 ...

  7. 支付宝app支付java后台流程、原理分析(含nei wang chuan tou)

    java版支付宝app支付流程及原理分析 本实例是基于springmvc框架编写     一.流程步骤         1.执行流程           当手机端app(就是你公司开发的app)在支付 ...

  8. 关于spring,IOC和AOP的解析原理和举例

    引用自:https://blog.csdn.net/paincupid/article/details/43152397 IOC:就是DAO接口的实现不再是业务逻辑层调用工厂类去获取,而是通过容器(比 ...

  9. 《CI/CD 流程以及原理说明》

    自动化部署 CI/CD 是一种通过在应用开发阶段引入自动化来频繁向客户交付应用的方法.CI/CD 的核心概念是持续集成.持续交付和持续部署.作为一个面向开发和运营团队的解决方案,CI/CD 主要针对在 ...

  10. Spring AOP 的实现 原理

    反射实现 AOP 动态代理模式实例说明(Spring AOP 的实现 原理)   比如说,我们现在要开发的一个应用里面有很多的业务方法,但是,我们现在要对这个方法的执行做全面监控,或部分监控.也许我们 ...

随机推荐

  1. excel表格里面数据统计有几个(相同的算1个)

    例如:1 2 3 4 5 6 7 1 2 3 统计出来的结果 是 7个! 相同的算1个. 假设数据在A1:A10区域内,在B1单元格中显示结果,则在B1单元格中输入公式: =SUMPRODUCT(1/ ...

  2. 数据库中的WITH temp_a 类似临时表的使用-CTE表达式

    个人随笔记录 当一个表的数据量过大时,在字段没有索引的情况下,查询的效率通常都会比较慢,如何能在不改变当前表结构的情况下让查询效率提升呢? 在一次偶然的巧合中看见同事使用了一个未见过语句,后面通过百度 ...

  3. 在SOUI中将自定义配置信息写到布局文件中

    SOUI的布局XML文件保存布局必须的信息.特定场合中,用户可能会需要在布局中指定业务需要处理的属性. 比如启程输入法的皮肤.有的皮肤支持高分屏,有的皮肤不支持.对于这个场景,比较理想的方案是直接在皮 ...

  4. Linux环境下安装phantomjs

    一.创建文件夹,用来存放软件 cd /opt/softWare mkdir  phantomJS cd phantomJS 二.下载并解压 wget https://bitbucket.org/ari ...

  5. DeepSeek+AnythingLLM,搭建本地AI知识库,真的太香了!三分钟搞定智能助手,小白也能轻松上手!

    1. 痛点暴击:你的知识管理还在原始时代吗? 你是否每次查找文档翻遍文件夹,会议纪要总在关键时刻"失踪"? 别慌!今天揭秘一个"真香"组合--DeepSeek+ ...

  6. SWD下载口的端口状态

    1.关于SWD SWD下载口的端口状态:SWD为上拉,SWC为下拉. SWD是MCU下载程序和调试的端口,分为四线制和五线制 四线制:VCC GND SWDIO SWCKL 五线制:VCC GND S ...

  7. 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!

    前言 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单.该份教程旨在通过AI技术重构传统科研模式,提升研究效率与智能化水平. DeepSeek访问地址:ht ...

  8. 使用Go复刻skiplist核心功能

    0.引言 正好做LC每日一题要求实现一个跳表,于是学习了redis的扩展skiplist,并使用Go进行复刻学习.学习参考了文章:Redis内部数据结构详解(6)--skiplist - 铁蕾的个人博 ...

  9. MySQL - [07] 查看库表数据所使用的空间大小

    1.切换数据库:use information_schema; 2.查看数据库使用大小 SELECT concat(round(sum(data_length/1024/1024),2),'MB') ...

  10. 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程序,新手也能快速上手!

    大家好,我是狂师. 在当今数字化时代,智能客服已成为提升用户体验.提高运营效率的关键工具. 今天,我们将为大家带来一个超级简单的教程,教你如何在短短3分钟内,利用腾讯微搭平台,将满血 DeepSeek ...