Spring5应用之JDK动态代理

作者简介:☕️大家好,我是Aomsir,一个爱折腾的开发者!
个人主页:Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客
当前专栏:Spring5应用专栏_Aomsir的博客-CSDN博客

文章目录

  • 前言
  • JDK动态代理
    • 开发步骤
    • 方法原型分析
      • ClassLoader
      • Class<?>[]
      • InvocationHandler
  • 总结
  • 参考文献

前言

在我们之前的探索中,已经详细解读了AOP如何借助动态字节码技术来构建动态代理类。实际上,实现动态代理的方式不止一种。其中,JDK动态代理CglibASMJavassist都是业界常用的技术手段。今天,我将引导大家深入 Spring AOP的底层原理,揭示其背后所采用的动态代理技术是如何工作的。为了更加系统地呈现这一内容,我特地选取了JDK动态代理与Cglib这两大主流方法,进行详实的解读。首先,我们将着重了解JDK动态代理的核心原理和实际应用情境。我的目标是,希望大家在了解这些深入的分析后,能够更为全面和深入地理解动态代理背后的精妙设计和实现

JDK动态代理

开发步骤

在之前关于AOP动态代理的探讨中,我们了解到创建AOP代理涉及三大关键要素:原始对象额外功能,以及一个代理对象该代理对象与原始对象共同实现相同的接口。这种结构在更为底层JDK动态代理的开发中也得到了体现。

考虑以下示例:其中,UserService代表原始对象实现的接口;UserServiceImpl是具体的原始对象,而通过InvocationHandler我们为其定义了额外功能。最终,我们利用Proxy.newProxyInstance方法成功地创建了代理类对象。

当我们运行这段代码,测试的输出结果与我们预期的完全一致,证明了我们成功地为原始对象UserServiceImpl注入了额外功能,而这一切都得益于JDK提供的动态代理机制
在这里插入图片描述

public class TestJDKProxy {private static final Logger log = LoggerFactory.getLogger(TestJDKProxy.class);@Testpublic void test1() {// 创建原始对象UserService userService = new UserServiceImpl();// 创建JDK动态代理对象UserService userServiceProxy = (UserService) Proxy.newProxyInstance(TestJDKProxy.class.getClassLoader(),userService.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {log.debug("log before");// 执行原始方法(涉及反射代码),获取其返回值Object ret = method.invoke(userService, args);log.debug("log after");return ret;}});userServiceProxy.login("admin", "123456");}
}

方法原型分析

从上述示例中,我们可以清晰地看到JDK动态代理对象的创建核心——Proxy.newProxyInstance()方法。这个方法接收三个关键参数:ClassLoaderClass<?>[]InvocationHandler。它们分别代表了类加载器原始类实现的接口的Class对象,以及用于注入的额外功能

接下来我将带着深入了解这三个参数的工作原理和用途,这对于我们完整地理解JDK动态代理机制是至关重要的。希望通过这次详尽的分析,大家能够对JDK动态代理有一个更为深入和全面的认识
在这里插入图片描述

ClassLoader

ClassLoader,即类加载器,在Java中起着至关重要的作用。但要深入了解它,首先必须回顾Java程序的标准运行流程。典型情况下,当程序启动时,类加载器首先会读取类对应的字节码文件(.class文件),将其加载到JVM中。随后,JVM会基于这些字节码数据,通过类加载器创建出对应的Class对象,并根据需要进一步实例化为具体对象

这个流程在遇到动态代理时遭遇了挑战。动态代理,顾名思义,其类是在运行时动态生成的,它并没有预先准备好的.class文件。那么,如何为这样的代理类创建一个Class对象呢?又或者说,ClassLoader在这里扮演什么角色?

实际上,当我们请求JVM创建一个动态代理时,JVM会为我们“临时”生成这个代理类的字节码。这并不是从文件系统中读取的,而是基于我们给定的接口和实现,即时生成的。 在这里,ClassLoader的任务是加载这个“临时生成”的字节码到JVM的内存中。这意味着,尽管代理类的字节码并没有物理存在,但ClassLoader依然可以处理它,就像处理其他常规Java类一样。

但这里有一个细节值得注意:这个用于加载动态代理的ClassLoader并不是新创建的,而是借用了现有的一个类加载器。这点尤为重要,因为在Java项目中,每个类都有它自己对应的类加载器,确保了类的隔离和安全性。在动态代理的场景中,我们实际上是复用了某个现有类的加载器来加载代理类,确保代理类能够顺利地与原始 类在同一个上下文中工作

Class<?>[]

Proxy.newProxyInstance()方法中,第二个参数Class<?>[]起着至关重要的作用。它是一个Class对象的数组,代表了一组接口。当我们希望创建一个动态代理对象时,这些接口定义了创建的代理对象将额外功能加在哪些原始类方法上

为什么是接口而不是具体的类呢?这是因为JDK的动态代理机制建立在接口的基础之上。具体来说,动态代理生成的代理类会实现指定的一组接口,而不是继承某个类。这使得动态代理具有很大的灵活性,因为一个Java类可以实现多个接口,但只能继承一个父类

通过传递一个Class对象的数组作为参数,我们告诉JVM我们希望代理类实现哪些接口,将额外功能加在哪些原始类方法上。然后,动态生成的代理类将会实现这些接口,并在每个接口方法的实现中,根据我们的需求,调用InvocationHandler来处理方法调用。

简而言之,Class<?>[]参数为Proxy.newProxyInstance()方法提供了一个蓝图,说明代理类应如何构建,并且定义了其行为特征

InvocationHandler

InvocationHandler是JDK动态代理机制中的一个关键接口,其定义了如何在代理对象上处理方法调用。该接口中,仅包含一个名为invoke的方法。此方法在设计上,旨在调用原始对象的方法,同时为其注入额外的功能

当代理对象上的一个方法被调用时,invoke方法就会被触发。它提供了我们一个场所,允许我们在原始方法执行前后添加自定义的行为或功能,从而扩展或改变原始方法的行为。
关于invoke方法的三个参数,它们分别为:

  1. Proxy:这是正在调用的方法所属的代理实例。大多数情况下,我们并不直接使用它,因为在InvocationHandler实现内部调用该代理对象会导致无限递归。
  2. Method:这代表了被代理对象的某个具体方法的反射对象它为我们提供了调用原始对象方法的能力,这可以通过method.invoke(targetObject, args)来完成,其中targetObject是原始对象的实例
  3. Object[]:这是被代理方法的参数数组,表示在代理对象上调用方法时传递的参数。

通过组合上述三个参数,我们可以在invoke方法中灵活地调用原始方法,同时根据需要为其添加额外的逻辑或功能,从而实现对原始方法行为的定制。

在我们深入了解JDK的InvocationHandler接口后,不禁让人回想起Spring AOP中的一个相似结构——MethodInterceptor接口。Spring AOP在动态代理实现中提供了这个接口,它与JDK的动态代理机制的核心思想相似,但是Spring对其进行了封装。

MethodInterceptor接口的设计是简洁而聚焦的。它的中心是一个invoke方法,这个方法的目的与InvocationHandler中的invoke相似: 拦截并增强方法调用。但不同的是,Spring选择了一个集成的方法来传递信息。而不是分开的多个参数,MethodInterceptor的invoke方法接受一个封装了方法调用详情的MethodInvocation对象。这个对象包含了调用的方法、目标对象、参数等所有必要的信息,而且还提供了一个proceed方法,用于执行原始的方法调用。
在这里插入图片描述

这种设计方式优雅地集合了所有的方法调用信息,使得代理的实现既简洁又集中。开发者不再需要从多个参数中提取信息,而是可以直接与MethodInvocation对象交互,从而更直观、高效地实现代理逻辑。

总结来说,Spring的MethodInterceptor接口提供了一种高效、简化的方式来实现动态代理,使得开发者可以更聚焦于业务逻辑的增强,而不是方法调用的底层细节

package java.lang.reflect;
public interface InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}
package org.aopalliance.intercept;@FunctionalInterface
public interface MethodInterceptor extends Interceptor {Object invoke(MethodInvocation var1) throws Throwable;
}

总结

今天我们详细探讨了Spring动态代理开发中的JDK动态代理机制。这个机制在Java的核心库中有着深厚的根基,而Spring则充分利用了它,为我们带来了灵活且强大的AOP实现。通过对JDK动态代理的深入学习,我们不仅加深了对Java代理工作原理的理解,也为接下来的学习打下了坚实的基础。在接下来的文章中,我们将转向另一个同样重要但工作机制截然不同的动态代理技术——Cglib。希望大家能继续保持热情,与我一起进一步探索Spring AOP背后的精妙技术

参考文献

  • 孙哥孙帅suns说Spring5~学不会Spring? 因为你没找对人
  • Spring官方文档

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

环形链表[简单]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给你一个链表的头节点head&#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪next指针再次到达&#xff0c;则链表中存在环。为了表示给定链表中的环&#xff0c;评测系统内部使用整数pos来表示链…

嵌入式开源库之libmodbus学习笔记

socat 安装sudo apt-get install socat创建终端 socat -d -d pty,b115200 pty,b115200查看终端 ls /dev/pts/ minicom 安装 sudo apt-get install minicom链接虚拟终端 sudo minicom -D /dev/pts/3以十六进制显示 minicom -D /dev/pts/1 -H设置波特率 minicom -D /dev/pts/1…

python操作.xlsx文件

from openpyxl import load_workbook from openpyxl.styles import Font,colors, Alignment from openpyxl.styles import Border, Side #打开已经存在的Excel workbook load_workbook(filenameC:\\Users\\yh\\Documents\\测试.xlsx) #创建表&#xff08;sheet&#xff09;,插…

【机器学习】熵和概率分布,图像生成中的量化评估IS与FID

详解机器学习中的熵、条件熵、相对熵、交叉熵 图像生成中常用的量化评估指标通常有Inception Score (IS)和Frchet Inception Distance (FID) Inception Score (IS) 与 Frchet Inception Distance (FID) GAN的量化评估方法——IS和FID&#xff0c;及其pytorch代码

STM32G070RBT6-MCU温度测量(ADC)

1、借助STM32CubeMX生成系统及外设相关初始化代码。 在以上配置后就可以生成相关初始化代码了。 /* ADC1 init function */ void MX_ADC1_Init(void) {/* USER CODE BEGIN ADC1_Init 0 *//* USER CODE END ADC1_Init 0 */ADC_ChannelConfTypeDef sConfig {0};/* USER COD…

pandas读取文件的时候出现‘OSError: Initializing from file failed’

报错原因&#xff1a; pandas.read_csv() 报错 OSError: Initializing from file failed&#xff0c;一般由两种情况引起&#xff1a;一种是函数参数为路径而非文件名称&#xff0c;另一种是函数参数带有中文。 原代码&#xff1a; data pd.read_csv(csv文件.csv) data导入文…

QT:绘图

widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPaintEvent> //绘图事件class Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent 0);~Widget();void paintEvent(QPaintEvent *event); //重写绘图事件void timerEve…

计算机竞赛 深度学习猫狗分类 - python opencv cnn

文章目录 0 前言1 课题背景2 使用CNN进行猫狗分类3 数据集处理4 神经网络的编写5 Tensorflow计算图的构建6 模型的训练和测试7 预测效果8 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习猫狗分类 ** 该项目较为新颖&a…

【自定义类型】--- 位段、枚举、联合

&#x1f493;博客主页&#xff1a;江池俊的博客⏩收录专栏&#xff1a;C语言进阶之路&#x1f449;专栏推荐&#xff1a;✅C语言初阶之路 ✅数据结构探索&#x1f4bb;代码仓库&#xff1a;江池俊的代码仓库&#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐ 文…

OCI 发布了容器运行时和镜像规范!

7 月 19 日是开放容器计划Open Container Initiative&#xff08;OCI&#xff09;的一个重要里程碑&#xff0c;OCI 发布了容器运行时和镜像规范的 1.0 版本&#xff0c;而 Docker 在这过去两年中一直充当着推动和引领的核心角色。 我们的目标是为社区、客户以及更广泛的容器行…

问答区混赏金的集合贴

此贴专记录CSDN问答社区里面&#xff0c;一些回答者在临近结题时胡乱回答&#xff0c;只为分取结题赏金的人。 为了截图方便&#xff0c;给回答者点赞和点踩不是对其回答的认可和不认可&#xff0c;只是为了方便截图而已 文章目录 第一位——夜深人静的哝玛 (PS:与本人的头像和…

为什么都说NFS读写性能差,如何进行优化?

使用基于NFS协议存储系统的同学经常遇到的问题是在小文件比较多的情况下性能会比较差。小文件访问性能差本身是可以理解的,但是NFS确实是太差了。不知大家是否深层次分析过,为什么NFS访问小文件性能会这么差? NFS文件系统与本地文件系统的差异在于多了一个网络传输的过程。…

基于or-tools的人员排班问题建模求解(JavaAPI)

使用Java调用or-tools实现了阿里mindopt求解器的案例&#xff08;https://opt.aliyun.com/platform/case&#xff09;人员排班问题。 这里写目录标题 人员排班问题问题描述数学建模编程求解&#xff08;ortoolsJavaAPI&#xff09;求解结果 人员排班问题 随着现在产业的发展&…

OpenGL之光照贴图

我们需要拓展之前的系统,引入漫反射和镜面光贴图(Map)。这允许我们对物体的漫反射分量和镜面光分量有着更精确的控制。 漫反射贴图 我们希望通过某种方式对物体的每个片段单独设置漫反射颜色。我们仅仅是对同样的原理使用了不同的名字:其实都是使用一张覆盖物体的图像,让我…

通讯网关软件013——利用CommGate X2ORACLE实现Modbus RTU数据转储ORACLE

本文介绍利用CommGate X2ORACLE实现从Modbus RTU设备读取数据并转储至ORACLE数据库。CommGate X2ORACLE是宁波科安网信开发的网关软件&#xff0c;软件可以登录到网信智汇(wangxinzhihui.com)下载。 【案例】如下图所示&#xff0c;实现从Modbus RTU设备读取数据并转储至ORACL…

洛谷P5732 【深基5.习7】杨辉三角题解

目录 题目【深基5.习7】杨辉三角题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1传送门 代码解释亲测 题目 【深基5.习7】杨辉三角 题目描述 给出 n ( n ≤ 20 ) n(n\le20) n(n≤20)&#xff0c;输出杨辉三角的前 n n n 行。 如果你不知道什么是杨辉三角&#xf…

Android Jetpack组件架构:ViewModel的原理

Android Jetpack组件架构&#xff1a;ViewModel的原理 导言 本篇文章是关于介绍ViewModel的&#xff0c;由于ViewModel的使用还是挺简单的&#xff0c;这里就不再介绍其的基本应用&#xff0c;我们主要来分析ViewModel的原理。 ViewModel的生命周期 众所周知&#xff0c;一般…

【进阶C语言】自定义类型

本节内容大致目录如下&#xff1a; 1.结构体 2.位段 3.枚举 4.联合&#xff08;共用体&#xff09; 以上都是C语言中的自定义类型&#xff0c;可以根据我们的需要去定义。 一、结构体 一些基础知识在初阶C语言的时候已经介绍过&#xff0c;在这里粗略概括&#xff1b;重…

C++17中std::filesystem::directory_entry的使用

C17引入了std::filesystem库(文件系统库, filesystem library)。这里整理下std::filesystem::directory_entry的使用。 std::filesystem::directory_entry&#xff0c;目录项&#xff0c;获取文件属性。此directory_entry类主要用法包括&#xff1a; (1).构造函数、…

Flutter笔记:滚动之-无限滚动与动态加载的实现(GetX简单状态管理版)

Flutter笔记 无限滚动与动态加载的实现&#xff08;GeX简单状态管理版&#xff09; 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq…