聊聊JDK动态代理原理

1. 示例

首先,定义一个接口:

public interface Staff {void work();
}

然后,新增一个类并实现上面的接口:

public class Coder implements Staff {@Overridepublic void work() {System.out.println("认真写bug……");}
}

假设现在有这么一个需求:在不改动以上类代码的前提下,对该方法增加一些前置操作或者后置操作。

接下来就来讲解下,如何使用JDK动态代理来实现这个需求。

首先,自定义一个调用处理器,实现java.lang.reflect.InvocationHandler接口并重写invoke方法:

public class AttendanceInvocationHandler implements InvocationHandler {private final Object target;public AttendanceInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("上班打卡……");Object invoke = method.invoke(target, args);System.out.println("下班打卡……");return invoke;}
}

重点看下Object invoke = method.invoke(target, args);,该行代码会执行真正的目标方法,在这前后,我们可以添加一些增强逻辑。

然后,新建个测试类,看下JDK动态代理如何使用:

public class JdkProxyTest {public static void main(String[] args) {Coder coder = new Coder();AttendanceInvocationHandler h = new AttendanceInvocationHandler(coder);// 创建代理对象Object proxyInstance = Proxy.newProxyInstance(coder.getClass().getClassLoader(),coder.getClass().getInterfaces(),h);Staff staff = (Staff) proxyInstance;staff.work();}
}

运行以上代码,效果如下图所示:

从运行结果可以看出,在目标方法的前后,执行了自定义的操作。

2. 原理

这里理解2个概念,目标对象和代理对象,

目标对象是真正要调用的对象,上面示例中的Coder类就是目标对象,

代理对象是JDK自动生成的对象,在代理对象内部会去调用目标对象的目标方法。

JDK动态代理的核心就是上面示例中的Proxy.newProxyInstance方法,方法签名如下图所示:

第1个参数传入的是目标对象的ClassLoader,第2个参数传入的是目标对象的接口信息,第3个参数传入的是自定义的InvocationHandler。

然后看下该方法的实现逻辑,先看第1处重点:

注释翻译过来是:查找或者生成指定的代理类。

该方法会生成代理类的字节码文件(也可能是从缓存中读取),核心逻辑在ProxyClassFactory类的apply方法中,

该方法中定义了生成的代理类的包名以及文件名:

因此默认情况下,自动生成的代理类名称是com.sun.proxy.$Proxy0

该方法最后会生成代理类的字节码,默认情况下不会保存到文件系统,但可以通过参数指定保存到文件系统:

可以看出,保存不保存到文件系统,受saveGeneratedFiles的影响,其定义如下所示:

private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));

所以可以通过指定sun.misc.ProxyGenerator.saveGeneratedFiles参数来让生成的代理类字节码文件保存到文件系统中。

然后看第2处重点:

先是获取构造函数,然后是生成代理类对象的实例。

3. 为什么必须要基于接口?

思考一个问题,为什么JDK动态代理必须要基于接口,带着这个问题,我们看下动态生成的代理类com.sun.proxy.$Proxy0长什么样子?

JVM参数里添加参数-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true,然后启动上面示例中的测试代码:

生成的代理类字节码文件保存在项目根目录下的com/sun/proxy目录下:

在IDEA中打开后,如下图所示:

在静态代码块中,对静态变量m0、m1、m2、m3进行了赋值,其中m3是要执行的目标方法。

在构造方法中,执行的是super(var1);,也就是父类Proxy的构造方法:

该方法是将我们自定义的InvocationHandler赋值给了父类的变量h。

而以下测试代码实际执行的是代理类$Proxy0里的work方法:

Staff staff = (Staff) proxyInstance;
staff.work();

代理类$Proxy0里的work方法实际执行的是自定义InvocationHandler里的invoke方法:

因此在执行目标方法前后,执行了自定义的前置操作和后置操作。

了解了这个调用过程,就理解了为什么JDK动态代理必须要基于接口,因为动态生成的代理类已经继承了类java.lang.reflect.Proxy

而Java又是单继承的,如果想要继续对类进行扩展,只能通过实现接口的方式。 

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

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

相关文章

一起学数据结构(3)——万字解析:链表的概念及单链表的实现

上篇文章介绍了数据结构的一些基本概念,以及顺序表的概念和实现,本文来介绍链表的概念和单链表的实现,在此之前,首先来回顾以下顺序表的特点: 1.顺序表特点回顾: 1. 顺序表是一组地址连续的存储单元依次存…

图像提示词攻略--基于 stable diffusion v2

Stable Diffusion 是一种潜在的文本到图像扩散模型,能够在给定任何文本输入(称为提示)的情况下生成逼真的图像。 在本文中,我将讨论和探索一些提高提示有效性的方法。从在提示中添加某些关键字和组合词、从更改单词顺序及其标点符…

24v转3.3v输出3A用什么芯片

问:客户需要一个能够将24V输入电压转换为3.3V输出电压,并且能够提供1-3A的电流输出的芯片。还希望它能够内置MOS管。有什么推荐的型号吗?(vin24v、5v,vout3.3v,Io1-3A) 答:推荐使用…

【福建事业单位-推理判断】08逻辑论证-加强-原因解释-日常总结

福建事业单位-推理判断】08逻辑论证-加强 一、加强题1.1 建立联系——搭桥1.2 补充论据必要条件(没它不行)补充论据(解释原因和举例论证) 总结 二、原因解释题三、日常结论复习建议 一、加强题 加强的题型,一般只加强…

替换开源LDAP,某科技企业用宁盾目录统一身份,为业务敏捷提供支撑

客户介绍 某高科技企业成立于2015年,是一家深耕于大物流领域的人工智能公司,迄今为止已为全球16个国家和地区,120余家客户打造智能化升级体验,场景覆盖海陆空铁、工厂等货运物流领域。 该公司使用开源LDAP面临的挑战 挑战1 开源…

详解Kafka分区机制原理|Kafka 系列 二

Kafka 系列第二篇,详解分区机制原理。为了不错过更新,请大家将本号“设为星标”。 点击上方“后端开发技术”,选择“设为星标” ,优质资源及时送达 上一篇文章介绍了 Kafka 的基本概念和术语,里面有个概念是 分区(Part…

高翔《自动驾驶中的SLAM技术》代码详解 — 第6章 2D SLAM

目录 6.2 扫描匹配算法 6.2.1 点到点的扫描匹配 6.2 扫描匹配算法 6.2.1 点到点的扫描匹配 // src/ch6/test_2dlidar_io.cc // Created by xiang on 2022/3/15. // #include <gflags/gflags.h> #include <glog/logging.h> #include <opencv2/highgui.hpp>…

解释器模式(Interpreter)

解释器模式是一种行为设计模式&#xff0c;可以解释语言的语法或表达式。给定一个语言&#xff0c;定义它的文法的一种表示&#xff0c;然后定义一个解释器&#xff0c;使用该文法来解释语言中的句子。解释器模式提供了评估语言的语法或表达式的方式。 Interpreter is a behav…

行业追踪,2023-08-07

自动复盘 2023-08-07 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

ros tf

欢迎访问我的博客首页。 tf 1. tf 命令行工具1.1 发布 tf1.2 查看 tf 2.参考 1. tf 命令行工具 1.1 发布 tf 我们根据 cartographer_ros 的 launch 文件 backpack_2d.launch 写一个 tf.launch&#xff0c;并使用命令 roslaunch cartographer_ros tf.launch 启动。该 launch 文件…

【Renpy】设置选项不满足条件禁止选择

【要求】如果某个属性不满足某个要求&#xff0c;则无法选择这个选项。 【版本】Renpy 8.1.1 【实现】 1.在options.rpy文件中添加 define config.menu_include_disabled True 2.在选项中增加if条件。 menu:"Yes" if money > 20: ##如果money小于20这个选项…

3.01 用户在确认订单页收货地址操作

用户在确认订单页面&#xff0c;可以针对收货地址做如下操作&#xff1a; 1. 查询用户的所有收货地址列表 2. 新增收货地址 3. 删除收货地址 4. 修改收货地址 5. 设置默认地址步骤1&#xff1a;创建对应用户地址BO public class AddressBO {private String addressId;private…

高抗干扰LCD液晶屏驱动芯片,低功耗的特性适用于水电气表以及工控仪表类产品

VK2C23是一个点阵式存储映射的LCD驱动器&#xff0c;可支持最大224点&#xff08;56SEGx4COM&#xff09;或者最大416点&#xff08;52SEGx8COM&#xff09;的LCD屏。单片机可通过I2C接口配置显示参数和读写显示数据&#xff0c;也可通过指令进入省电模式。其高抗干扰&#xff…

zookeeper+kafka

目录 Kafka概述 一、为什么需要消息队列&#xff08;MQ&#xff09; 二、使用消息队列的好处 三、消息队列的两种模式 四、Kafka 定义 五、Kafka 简介 六、Kafka 的特性 七、Kafka 系统架构 分区的原因 八、部署kafka 集群 1.下载安装包 2.安装 Kafka 3.修改…

风控安全产品系统设计的一些思考

背景 本篇文章会从系统架构设计的角度&#xff0c;分享在对业务安全风控相关基础安全产品进行系统设计时遇到的问题难点及其解决方案。 内容包括三部分&#xff1a;&#xff08;1&#xff09;风控业务架构&#xff1b;&#xff08;2&#xff09;基础安全产品的职责&#xff1…

mysql8查看执行sql历史日志、慢sql历史日志,配置开启sql历史日志general_log、慢sql历史日志slow_query_log

0.本博客sql总结 -- 1.查看参数 -- 1.1.sql日志和慢sql日志输出方式(TABLE/FILE)。global参数 SHOW GLOBAL VARIABLES LIKE log_output; -- 1.2.sql日志开关。global参数 SHOW GLOBAL VARIABLES LIKE general_log%; -- 1.3.慢sql日志开关。global参数 SHOW GLOBAL VARIABLE…

AWS Amplify 部署node版本18报错修复

Amplify env&#xff1a;Amazon Linux:2 Build Error : Specified Node 18 but GLIBC_2.27 or GLIBC_2.28 not found on build 一、原因 报错原因是因为默认情况下&#xff0c;AWS Amplify 使用 Amazon Linux:2 作为其构建镜像&#xff0c;并自带 GLIBC 2.26。不过&#xff0c;…

UNIX 入门

与 UNIX 建立连接启动会话登录命令提示符修改口令退出系统 简单的 UNIX 命令命令格式ls 命令who 命令虚拟终端 tty伪终端 ptywho am i 命令 cal 命令help 命令man 命令 shell 概述shell 命令更换 shell临时更改 shell永久更改 shell 登录过程 与 UNIX 建立连接 启动会话 要启…

RabbitMQ 备份交换机和死信交换机

为处理生产者生产者将消息推送到交换机中&#xff0c;交换机按照消息中的路由键即自身策略无法将消息投递到指定队列中造成消息丢失的问题&#xff0c;可以使用备份交换机。 为处理在消息队列中到达TTL的过期消息&#xff0c;可采用死信交换机进行消息转存。 通过上述描述可知&…

P1049 [NOIP2001 普及组] 装箱问题(背包)(内附封面)

[NOIP2001 普及组] 装箱问题 题目描述 有一个箱子容量为 V V V&#xff0c;同时有 n n n 个物品&#xff0c;每个物品有一个体积。 现在从 n n n 个物品中&#xff0c;任取若干个装入箱内&#xff08;也可以不取&#xff09;&#xff0c;使箱子的剩余空间最小。输出这个最…