spring5源码篇(10)——spring-aop代理过程

spring-framework 版本:v5.3.19

文章目录

  • 1、ProxyFactory
    • 1.1、createAopProxy() 创建AopProxy
    • 1.2、getProxy() 创建代理对象
    • 1.3、JdkDynamicAopProxy#invoke 代理逻辑
      • 1.3.1、advised.getInterceptorsAndDynamicInterceptionAdvice() 匹配添加的advisor并转化成所需对象
      • 1.3.2、invocation.proceed() 执行代理逻辑
  • 2、DefaultAdvisorAutoProxyCreator
  • 3、AnnotationAwareAspectJAutoProxyCreator

废话少说,上代码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在spring-aop中,无论是xml还是注解还是硬编码配置的代理,最后都会通过proxyFactory去实现真正的代理。
proxyFactory的使用也很简单,给他一个target和一个advisor即可生成代理类。

target就是被代理对象,而 advisor 可以简单的理解成 advice + pointcut

1、ProxyFactory

所以proxyFctory是如何生成代理对象的?

追踪ProxyFactory.getProxy()方法
在这里插入图片描述

1.1、createAopProxy() 创建AopProxy

这里可能创建 CglibAopProxy 也可能创建 JdkDynamicAopProxy,分别对应两种不同的动态代理方式

AopProxy 由 AopProxyFactory生成,而spring默认的AopProxyFactory只有 DefaultAopProxyFactory 这一个实现。
在这里插入图片描述
通俗的说就是:如果目标对象至少实现了一个接口则使用jdk,反之使用cglib。同时也可配置 optimize = true 或者 proxyTargetClass = true 强制使用cglib代理。

1.2、getProxy() 创建代理对象

这里就是用cglib或者是jdk的方式去创建一个代理对象了。以jdk为例,其实就是我们平时使用jdk动态代理的代码。
JdkDynamicAopProxy.getProxy()
在这里插入图片描述
注意看 Proxy.newProxyInstance 的 InvocationHandler 传了this,说明 JdkDynamicAopProxy 同时也实现了 InvocationHandler 。
也就是最最最重要的代理逻辑就在 JdkDynamicAopProxy.invoke 方法。

1.3、JdkDynamicAopProxy#invoke 代理逻辑

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;TargetSource targetSource = this.advised.targetSource;Object target = null;try {//特殊方法以及对代理对象的配置信息进行操作,如不代理equals,hashCodeif (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {return equals(args[0]);}else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {return hashCode();}else if (method.getDeclaringClass() == DecoratingProxy.class) {return AopProxyUtils.ultimateTargetClass(this.advised);}else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);}Object retVal;//如果配置 exposeProxy = true 则把当前代理对象放到线程ThreadLocalMap中// 当想要自己调自己同时代理不失效的时候可以配为trueif (this.advised.exposeProxy) {oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}target = targetSource.getTarget();Class<?> targetClass = (target != null ? target.getClass() : null);//匹配添加的advisorList<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);if (chain.isEmpty()) {//如果为空直接反射调用Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);}else {//不为空则进行代理MethodInvocation invocation =new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);retVal = invocation.proceed();}//对返回值的检查及特殊情况的处理Class<?> returnType = method.getReturnType();if (retVal != null && retVal == target &&returnType != Object.class && returnType.isInstance(proxy) &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {//特殊情况:当返回自身时,则返回代理后的对象retVal = proxy;}else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);}return retVal;}finally {if (target != null && !targetSource.isStatic()) {targetSource.releaseTarget(target);}if (setProxyContext) {AopContext.setCurrentProxy(oldProxy);}}}

1.3.1、advised.getInterceptorsAndDynamicInterceptionAdvice() 匹配添加的advisor并转化成所需对象

在这里插入图片描述

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Advised config, Method method, @Nullable Class<?> targetClass) {AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();Advisor[] advisors = config.getAdvisors();List<Object> interceptorList = new ArrayList<>(advisors.length);Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());Boolean hasIntroductions = null;//分 PointcutAdvisor、IntroductionAdvisor、其他 三个分支for (Advisor advisor : advisors) {if (advisor instanceof PointcutAdvisor) {//PointcutAdvisor分支PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;//匹配 prefiltered 和 classif (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {//匹配 methodMethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();boolean match;if (mm instanceof IntroductionAwareMethodMatcher) {//如果MethodMatcher是IntroductionAwareMethodMatcher的话,则用IntroductionAwareMethodMatcher.matchesif (hasIntroductions == null) {hasIntroductions = hasMatchingIntroductions(advisors, actualClass);}match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);}else {//反之直接用MethodMatcher.matchesmatch = mm.matches(method, actualClass);}if (match) {//如果匹配则将 advisor 转化成 MethodInterceptor[]MethodInterceptor[] interceptors = registry.getInterceptors(advisor);if (mm.isRuntime()) {// 如果runtime为true,说明需要根据参数动态匹配,添加 InterceptorAndDynamicMethodMatcherfor (MethodInterceptor interceptor : interceptors) {//InterceptorAndDynamicMethodMatcher = MethodInterceptor + MethodMatcherinterceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));}}else {// 反之直接加到listinterceptorList.addAll(Arrays.asList(interceptors));}}}}else if (advisor instanceof IntroductionAdvisor) {//IntroductionAdvisor分支IntroductionAdvisor ia = (IntroductionAdvisor) advisor;//匹配 prefiltered 和 classif (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {// 如果匹配 将advisor转成Interceptor[] 放到listInterceptor[] interceptors = registry.getInterceptors(advisor);interceptorList.addAll(Arrays.asList(interceptors));}}else {//其他类型的Advisor分支Interceptor[] interceptors = registry.getInterceptors(advisor);interceptorList.addAll(Arrays.asList(interceptors));}}return interceptorList;}

至于是如何转化的,用了适配器模式。遍历所有 AdvisorAdapter,如果这个适配器可以转化,则进行转化。注意看,这里就算适配了也没有退出循环,也就是说一个advisor可能同时被多个适配器适配,这也就是为什么会返回数组的原因。
register.getInterceptors(advisor)
在这里插入图片描述
与其说转化advisor,我个人更喜欢理解成转化advice。
内置的适配器跟内置的advice是对应上的
在这里插入图片描述
以before为例
在这里插入图片描述
不过为什么没有环绕通知的适配器,因为我们用的AspectJAroundAdvice环绕通知本来就是一个MethodInterceptor
在这里插入图片描述

总之这一步会匹配符合的advisor并将其转化成所需对象(如MethodInterceptor)。匹配的逻辑根据advisor的类型分为三个分支:PointcutAdvisor、IntroductionAdvisor、其他,简单的说,他们的区别就是分别为匹配类和方法,匹配类,无需匹配直接添加。但无论是哪种分支,最终都会将advisor(直接或通过适配器)转化成 MethodInterceptor 或 InterceptorAndDynamicMethodMatcher。

1.3.2、invocation.proceed() 执行代理逻辑

在这里插入图片描述
为空的情况没什么好说的直接反射调用就是了。
不为空的情况下,会将所有代理所需要的变量封装成一个 MethodInvocation ,并递归调用proceed()执行代理逻辑。
invocation.proceed()
在这里插入图片描述
以beforeAdvice转化的MethodBeforeAdviceInterceptor为例看一下是如何递归的。
在这里插入图片描述
至此,proxyFactory的jdk代理就看完了,cglib的代理也是类似的。无非就是生成代理对象时所用的技术不同而已,像什么advisor,AdvisorAdapter,methodInterceptor,InterceptorAndDynamicMethodMatcher,methodInvocation,等都是一样的。

总结上面的内容粗略画了一个流程图:
在这里插入图片描述

2、DefaultAdvisorAutoProxyCreator

不过我们平时使用spring-aop的时候貌似没有用到proxyFactory。而是只需配置advisor bean或者使用aspectj注解配置切面就可以了。
比如这样
在这里插入图片描述
为什么这里容器得到了bean会是一个代理类呢?什么时候给他代理了?
答案就在import的 DefaultAdvisorAutoProxyCreator
在这里插入图片描述
DefaultAdvisorAutoProxyCreator 本质上是一个SmartInstantiationAwareBeanPostProcessor,即BeanPostProcessor。在容器启动的时候会增强bean,所谓的增强其实就是自动扫描容器中匹配的advisor,并代理目标bean。

postProcessAfterInitialization 方法的增强逻辑在其抽象父类AbstractAutoProxyCreator
在这里插入图片描述

3、AnnotationAwareAspectJAutoProxyCreator

在这里插入图片描述
而 AnnotationAwareAspectJAutoProxyCreator 与 DefaultAdvisorAutoProxyCreator 最大的区别就在于寻找容器所有的advisor时,所使用的方法不同。 AnnotationAwareAspectJAutoProxyCreator 重写了findCandidateAdvisors() 使其支持将 aspectj 注解的内容转化成 advisor。
在这里插入图片描述
所以如果我们只是import了 DefaultAdvisorAutoProxyCreator 是不会去解析 aspectJ 对应的aop注解的。
只有当import了 AnnotationAwareAspectJAutoProxyCreator 才会去解析。这也正是 @EnableAspectJAutoProxy 注解所做的事情。
在这里插入图片描述

由代码也可以看出,当使用了 AnnotationAwareAspectJAutoProxyCreator 就没必要再使用 DefaultAdvisorAutoProxyCreator 了,因为前者不仅会解析 aspectJ 的注解生成advisor,同时也会像后者一样扫描当前已有的 advisor。

除了上面两种AutoProxyCreator,spring中还有 BeanNameAutoProxyCreator(根据bean name去自动创建),AspectJAwareAdvisorAutoProxyCreator(xml配置的aspectJ切面),InfrastructureAdvisorAutoProxyCreator(Infrastructure相关,如事务管理)。当然也可以自己去继承AbstractAdvisorAutoProxyCreator自定义自动代理创建逻辑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/2151.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于51单片机和proteus的电流采集系统

此系统是基于51单片机和proteus的仿真设计&#xff0c;功能如下&#xff1a; 1. LCD1602实时显示获取到电流值及设定值。 2. 按键可调整电流设定值。 3. 电流值过高则蜂鸣器报警。 4. 指示灯指示电流及系统状态。 5. 系统信息可通过串口实时更新。 功能框图如下&#xff1…

<C语言> 自定义类型

1.结构体 结构体是一种用户自定义的数据类型&#xff0c;允许将不同类型的数据项组合在一起&#xff0c;形成一个更大的数据结构。结构体可以包含多个成员变量&#xff0c;每个成员变量可以是不同的数据类型&#xff0c;如整数、字符、浮点数等&#xff0c;甚至可以包含其他结构…

用html+javascript打造公文一键排版系统5:二级标题排版

公文中二级标题的一般以&#xff08;X&#xff09;标注&#xff08;其中X为由"一二三四五六七八九十"中的字符组成的字符串&#xff09;&#xff0c;用楷体字加粗。 首先我们要判断一段文字是否包含二级标题&#xff0c;最简单的方法 就是判断文字中的头一个字符是否…

Unity中指定物体的模型面数分析

给定一架飞机模型&#xff0c;需要分析该模型中&#xff0c;各个3D物体的面数和三角形数&#xff0c;目的是用于观察哪些物体面数过多&#xff0c;需要减面。 一、模型面数分析 二、脚本代码 using System.Collections; using System.Collections.Generic; using UnityEngine…

ROS:action通信

目录 一、前言二、概念三、作用四、实际案例4.1需求4.2action通信自定义action文件4.2.1定义action文件4.2.2编辑配置文件4.2.3编译 4.3action通信自定义action文件调用(C)4.3.1流程4.3.2vscode配置4.3.3服务端4.3.4客户端4.3.5编译配置文件4.3.6执行 4.4action通信自定义actio…

字节跳动面试挂在2面,复盘后,决定二战.....

先说下我基本情况&#xff0c;本科不是计算机专业&#xff0c;现在是学通信&#xff0c;然后做图像处理&#xff0c;可能面试官看我不是科班出身没有问太多计算机相关的问题&#xff0c;因为第一次找工作&#xff0c;字节的游戏专场又是最早开始的&#xff0c;就投递了&#xf…

EXCEl——移除单元格中换行符

方法一&#xff1a;使用清除格式功能 步骤如下: 1.选中需要取消换行的单元格 2.在“开始"选项卡中找到"清除”功能&#xff0c;点击下拉菜单中的“清除格式" 3.这时单元格的换行就被取消了。 清除前效果图 清除后效果图 方法一&#xff1a;使用函数功能 步骤…

K8s入门

K8s入门 目录 K8s入门namespacepoddeployment多版本扩缩容治愈能力滚动更新版本回退 serviceClusterIPNodePort ingress域名访问路径重写流量限制 存储抽象PV&PVCConfigMapSecret namespace kubectl get ns # 获取命名空间 kubectl create ns 名字 # 创建命名空间 ku…

自动收小麦机(牛客2023萌新)

题目链接 示例1 输入 复制 4 1 2 1 1 4 5 2 2 2 3 4 输出 复制 10 说明 在第4格放出水流后&#xff0c;水流会流向第3格&#xff0c;由于第3格高度比第4格低&#xff0c;所以水流继续向左流向第2格&#xff0c;因为平地水流只能流2格&#xff0c;所以到达第2格后水流停…

【Fiddler】Fiddler实现mock测试(模拟接口数据)

软件接口测试过程中&#xff0c;经常会遇后端接口还没有开发完成&#xff0c;领导就让先介入测试&#xff0c;然后缩短项目时间&#xff0c;有的人肯定会懵&#xff0c;接口还没开发好&#xff0c;怎么介入测试&#xff0c;其实这就涉及到了我们要说的mock了。 一、mock原理 m…

【ArcGIS Pro二次开发】(49):村规数据入库【福建省】

之前用Arcpy脚本工具做了一个村规数据入库和主要图纸生成工具。 在使用过程中&#xff0c;感觉对电脑环境比较高&#xff0c;换电脑用经常会一些莫名其妙的错误&#xff0c;bug修得很累。近来随着ArcGIS Pro SDK的熟悉&#xff0c;就有了移植的想法。 这里先把村规数据入库工…

QT学习—【属于自己的】串口调试助手

文章目录 一、功能简介二、串口助手实现2.1 创建UI2.2 扫描可用串口2.3 配置波特率2.4 配置数据位2.5 配置停止位2.6 配置校验位2.7 打开/关闭串口2.8 刷新串口2.9 发送新行2.10 串口发送2.11 串口接收显示2.12 清空接收窗口2.13 定时发送2.14 固定窗口大小 三、总结3.1 将信号…

本地前端项目使用gitee仓库外链图片加载失败

错误&#xff1a;本地的前端项目&#xff0c;比如vue&#xff0c;纯html使用<img/>标签加载gitee保存的图片文件的时候&#xff0c;浏览器加载失败。 但是gitee可以正常访问图片 解决办法&#xff1a; 在index.html中加入meta标签就可以完美解决 <meta name"r…

centos升级龙蜥

centos升级龙蜥 龙蜥简介龙蜥官方社区centos升级龙蜥首先确认自己的centos版本下载迁移镜像源安装epel源迁移工具安装i686包查看执行迁移脚本结果查看重启机器查看系统信息 龙蜥简介 2021年10月19日的大会上&#xff0c;阿里云发布全新操作系统“龙蜥”并宣布开源。龙蜥操作系…

python 批量下载图片(协程)

要下载的图片网站 1、总共多少页&#xff0c;得到每页的 url 列表 2、每页的图片详情的 ulr 列表&#xff08;因为该高清大图在图片详情页&#xff0c;因此需要去图片详情页拿图片的url) ​​​​​​​ 3、进入图片详情页&#xff0c;获取到图片url 然后下载。 完整代码如下&…

涂鸦智能打造专业家庭智能生活助手,实现人机交互升级

近年来&#xff0c;智能家居设备的品类不断拓展&#xff0c;同时&#xff0c;人们对AI与智能家居的联动愈发憧憬。自然语言交互是未来人机交互的主要趋势之一&#xff0c;其关键在于使AI具备主动理解信息的能力&#xff0c;让用户的交互更轻松。如何将智能场景的交互变得更“善…

SpringBoot中通过自定义Jackson注解实现接口返回数据脱敏

场景 SpringBoot中整合Sharding Sphere实现数据加解密/数据脱敏/数据库密文&#xff0c;查询明文&#xff1a; SpringBoot中整合Sharding Sphere实现数据加解密/数据脱敏/数据库密文&#xff0c;查询明文_霸道流氓气质的博客-CSDN博客 上面讲的是数据库中存储密文&#xff0…

HTML+CSS+JavaScript:渲染柱形统计图

一、需求 用户输入四个季度的数据&#xff0c;根据数据生成柱形统计图&#xff0c;浏览器预览效果如下 二、完整代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content&q…

【图像分割 2023 CVPR】CFNet

文章目录 【图像分割 2023 CVPR】CFNet摘要1. 简介2. 相关工作2.1 稠密预测的主干网设计2.2 多尺度特征融合 3. 方法3.1 整体架构3.2 过渡块3.4 结构变体 【图像分割 2023 CVPR】CFNet 论文题目&#xff1a;CFNet: Cascade Fusion Network for Dense Prediction 中文题目&#…

音视频H265编码; Socket通信实现N对N连接代码示例

H.265编码和Socket通信是两个不同的概念&#xff0c;它们分别涉及视频编码和网络通信。在实现N对N连接时&#xff0c;您可以将它们结合起来&#xff0c;但要注意每个方面的具体实现。 H.265编码&#xff08;视频编码&#xff09;&#xff1a; H.265编码涉及将视频数据进行压缩…