聊聊JDK动态代理原理

news2024/12/28 3:36:45

1. 示例

首先,定义一个接口:

public interface Staff {
    void work();
}

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

public class Coder implements Staff {
    @Override
    public 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;
    }

    @Override
    public 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.coloradmin.cn/o/846837.html

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

相关文章

为什么说用C端产品的思维做B端产品就是死路一条?

经常听行业大佬说起:如果用C端产品的思维做B端产品就是死路一条,那原因究竟是什么呢? 首先,需要明确的是C端产品和B端产品的用户群体和需求存在很大差异。C端产品的用户主要是消费者,更多的是被情感驱动。而B端产品的…

Visual Studio 2022安装教程(英文版)

文章目录 1.下载安装 1.下载 官网地址:https://visualstudio.microsoft.com/zh-hans/vs/ 选择第一个社区版本:Community 2022 安装 1.将下载好的文件保存到桌面,双击点开 2.等待visual studio installer配置好 3.点击安装后会来到配件选…

消息队列(3) -封装数据库的操作

前言 上一篇博客我们写了, 关于交换机, 队列,绑定, 写入数据库的一些建库建表的操作 这一篇博客中,我们将建库建表操作,封装一下实现层一个类来供上层服务的调用 , 并在写完该类之后, 测试代码是否完整 实现封装 在写完上述的接口类 与 xml 后, 我们想要 创建一个类 ,来调用…

使用OpenCV进行目标提取详细教程(附python代码演练)

今天的文章将讨论并指导你识别图像中的对象,使用 OpenCV 对这些对象进行遮罩处理。让我们开始吧! HSV 色标 请花一点时间观察下面的图片。每个图块似乎是不同的颜色,对吧?但是有一个有趣的地方:如果我们仔细思考&#…

B2B2C跨境独立站后台管理--支付系统开源搭建

要搭建一个B2B2C跨境独立站后台管理的支付系统,您可以按照以下步骤进行开发: 1. 确定需求和功能:首先,您需要明确支付系统的功能和需求,包括接入第三方支付平台、实现支付功能、订单管理、退款管理、对账功能等。 2.…

无涯教程-Perl - eval函数

描述 该函数在执行时判断EXPR,就好像EXPR是一个单独的Perl脚本一样。这使您可以在程序中使用单独的,也许是用户提供的Perl脚本。每次调用函数时,都会分别判断eval EXPR语句。 当解析脚本的其余部分时(执行之前),第二种形式判断BLOCK。 语法 以下是此函数的简单语法- eval …

Redis探索之旅

目录 今日良言:有志者自有千计万计,无志者只感千难万难 一、简介 二、Redis的安装 三、Redis的简单使用 四、Redis相关知识点 1.缓存分类 2.五大基本数据类型使用 3.持久化 4.常见面试题 今日良言:有志者自有千计万计,无…

【怎么提高性能和解决高并发】

怎么解决高并发 解决高并发的整体流程大概是: 先进行性能评估、再进行性能测试、然后找到程序可以承受的临界点、最后针对出问题的地方,进行优化。当然硬件设置对高并发的影响也很重要,如果达到硬件天花板,那么再怎么优化程序都…

如何实现网络数据传输

目录 前言 1.理解源IP地址和目的IP地址 2.理解端口号 2.1端口号与进程pid的关系 2.2源端口号和目的端口号 3.协议 3.1TCP协议 3.2认识UDP协议 4.网络字节序 5.socket编程接口 总结 前言 在上一篇文章网络框架中给大家对网络的整体进行了一个宏观的介绍,这…

理解递归方法

递归相关问题 树和二叉树相关的大部分问题二分查找相关问题快速排序、归并排序相关问题所有回溯的问题所有动态规划的问题 本质与特征 本质 本质就是方法的调用,而且是方法自己调用自己。 特征 执行时范围不断缩小,这样才能触底反弹终止(结…

使用MIT Kerberos Ticket Manager在windows下浏览器访问hadoop页面

Author : Spinach | GHB Link : http://blog.csdn.net/bocai8058文章目录 前言准备配置说明安装Firefox浏览器安装MIT Kerberos Ticket Manager客户端配置krb5.ini文件配置MIT Kerberos Ticket Manager客户端配置Firefox浏览器代理参数 访问WebUI 前言 kerberos是一种计算机…

亚马逊关键词下单的作用

在亚马逊上,关键词对于商品的搜索和发现起着非常重要的作用。当卖家在亚马逊上发布商品时,他们可以使用相关的关键词来描述该商品,这些关键词通常是与该商品相关的词汇或短语。 关键词下单的作用如下: 1、商品搜索: 买…

vue3+antd——实现个人中心页面+同步更改头部用户信息——基础积累

之前写过一篇文章关于vue3antd的框架模板,链接如下:http://t.csdn.cn/9dZMS 首先感谢大神提供的后台管理系统的模板,在此基础上改动要简单很多,主要是自己有很多内容不太敢随意改动。。。 直接看【个人中心】页面的效果图&#…

JavaScript事件委托与事件流+牛客例题

事件流: 概念:事件完整执行过程中的流动路径 说明:假设页面里有个div,当触发事件时,会经历两个阶段,分别是捕获阶段、冒泡阶段 简单来说:捕获阶段是 从父到子 冒泡阶段是从子到父 注意&…

TEMU美国儿童文具亚马逊CPC测试标准

美国站儿童文具类上架跨境电商平台美国站或者出口美国需要提交CPC认证,才能进入美国市场,由CPSC 认可的实验室出具的检测报告,确认每件商品均已过检测,符合上述适用要求。但许多亚马逊卖家反映:在亚马逊卖的文具类产品…

哪个思维导图软件好,知道这5个就够了!

思维导图作为一种有效的组织和展示思维的工具,广泛应用于学习、项目管理、创意发展等领域。然而,手工绘制思维导图费时费力,限制了其在快节奏的现代生活中的应用。本文将介绍5款可以一键生成思维导图的软件,它将通过智能化的方式&…

Chrome 谷歌浏览器,自动填充密码,提示需要输入电脑开机密码问题

我们在使用浏览器访问各个网站时,经常会保存密码。在下一次访问时,直接使用保存的密码填充,简单方便。 但是突然有一天(怀疑是谷歌浏览器更新导致的),每次使用密码填充时,都有如下拦截 拦截提…

前端 select 标签如何创建下拉菜单?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 代码示例⭐ 代码讲解⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅!这个专栏是为那些对Web开发感兴趣、刚刚踏…

PyTorch中ReduceLROnPlateau的学习率调整优化器

PyTorch中ReduceLROnPlateau的学习率调整优化器 作者:安静到无声 个人主页 简介: 在深度学习中,学习率是一个重要的超参数,影响模型的收敛速度和性能。为了自动调整学习率,PyTorch提供了ReduceLROnPlateau优化器&…

Linux IPIP隧道连通两个局域网

拓扑结构 现有两台主机,它们具有两个网口分别接入到不同网络中。 主机A: eth0:处于 10.0.1.2/24 网段eth1: 处于192.168.1.100/24 网段 主机B: eth0:处于10.0.2.3/24 网段eth1: 处于192.168.2…