| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> JDK动态代理的笔记与学习思路 -> 正文阅读 |
|
[Java知识库]JDK动态代理的笔记与学习思路 |
????????三年CRUD下来,对于一些知识点的印象已经模糊,作为一个有梦想的CV战士,最近决定重操旧业(刷题面试),在这里记录下复习JDK动态代理(以下统称动态代理)时的笔记。 JDK动态代理是什么? ? ? ? 说到动态代理,第一反应肯定是代理模式,GOF23(23种设计模式)之一,动态代理的目的就是让代码自己生成代理对象,从而解放我们的双手,让我们只需要关心类本身和代理类的增强方法,而不需要再关心代理类和代理对象的创建过程。 动态代理的实现方式 ? ? ? ? 关于动态代理的实现,主要是一个接口(InvocationHandler)和一个类(Proxy) ????????首选需要有一个类来实现InvocationHandler接口。实现这个接口的类可以来进行对被代理对象进行代理增强操作 InvocationHandler接口? ? ? ? 关于这个接口,里面只有一个方法,就是invoke方法,看到这个方法中有三个参数,第一个是Object,第二个是Method,第三个是Object[] args
????????看到这是不是有点楞逼?三个参数最眼熟的应该是Method吧?对反射有所了解的应该知道,Method就是方法的意思,那另外两个是什么呢?不要急,我们点开这个接口的源码。大家可以看到,在源码中对这三个参数的解释 ? ? ? ? 这三个参数可以先根据注释翻译一下,再打个标记,等下去验证一番 标记一:重写invoke方法的三个参数到底指什么
????????这行英文翻译过来,意思就是——对其调用方法的代理实例 ????????这里应该已经明了了,这个就是动态代理自动生成出来的代理对象 ????????再来看第二个参数
? ? ? ? 翻译一下就是——代理实例调用的,实例对应的接口方法 ? ? ? ? 这个大致意思代理对象实现的接口的方法 ? ? ? ? 再来看第三个参数
? ? ? ? 翻译一下——大致就是方法执行时的参数 实现InvocationHandler接口的类????????知道这个接口的需要实现的方法后我们来写个类实现下这个接口 ?
? ? ? ? 首先可以看到我这个类里面有个变量Object target,这个变量就是需要被这个增强类UserProxy代理的接口。 ????????可以看到我这个UserProxy类实现了InvocationHandler接口,并实现invoke方法,然后我们可以看到我的方法里有个method.invoke(target,args),这个方法眼熟吧?反射执行某个方法是不是?这里就是我们代理对象真正执行被代理对象的方法的位置。我们在执行接口方法前,输出了两行语句,方便等下验证 ? ? ? ? 代理增强类写完了,接下来可以就需要写一下被代理类和被代理类实现的接口 创建被代理类和接口User接口
UserImpl类实现User接口
测试代理结果创建完了被代理对象,我们先写个测试类来验证下结果
运行这个测试类,得到的结果是
????????这里可以看到我们的增强类UserProxy中增加的方法已经被执行,那么让我们来看看这段代码是怎么创建代理类的对象的 ????????我们可以看到这里调用了Proxy.newProxyInstance方法去创建,那么先看下这个方法的参数,老规矩我们点进方法内部,可以看到这个方法有三个传入参数ClassLoader loader, Class<?>[] interfaces, InvocationHandler h
????????这里翻译一下,这个loader是个类加载器
????????这里翻译一下,interfaces这个参数就是代理对象实现的接口数组
????????这里翻译一下,h这个参数就是? 将方法调用分派到的调用处理程序 ,这个看起来比较拗口, 但是我们看下,h这个参数的类型是InvocationHandler,就是我们刚才的增强类UserProxy实现的接口。 ? ? ? ? 这样一来,这个创建代理类的方法中三个参数我们都已经知道是什么了。但是你以为这就结束了吗? ? ? ? ? 我们来看看这个Proxy创建出来的代理类对象到底是什么呢?让我们来debug一下。 ???????????为什么是null?null对象为什么会有执行方法?这里不该抛出空指针吗? ????????看来要开始新一轮的探索了,在这里我们打个标记 标记二:为什么代理对象的值是null Proxy类创建代理对象的流程????????首先让我们来看看Proxy类到底是怎么创建代理对象的 ????????点进方法后,我们直接跳过最前面那部分参数校验,来看到这个方法
????????老规矩,我们先来翻译一下注释,意思就是——查找或生成指定的代理类 ????????很明显这就是我们要找的方法了,那继续点进去,发现这里有个参数校验,校验接口数量是否超过65535(真的会有实现这么多接口的类吗。。) ????????过了参数校验,就进入了proxyClassCache.get(loader, interfaces)方法。 ????????看到proxyClassCache这个,首先想到的就是cache缓存,没错,进去后先会在缓存中查找是否已有代理类生成,如果没有再去生成,那我们直接就来看代理类的生成逻辑
????????我们来看这行代码,这里传入了我们的两个参数,构造器和接口,返回了一个非空的object ????????我们来找到这个方法的实现类ProxyClassFactory ????????我们继续,跳过上面一系列对名字的操作,往下看会看到这么一段代码
????????这里有个String变量叫做proxyName,看变量名字,是不是就是代理名称?我们再看下proxyClassNamePrefix这个参数,这个参数的值是一个常量"$Proxy",最后的num,国际通用简称。number。那这个参数是不是就很明显了,这是我们代理类的名称,就是我们刚才测试运行结果时打印出来的com.sun.proxy.$Proxy9这个类的类名。 ? ? ? ? 从这里开始往下看,我们可以看到这行代码
? ? ? ? 这个注释一点都不拗口,就是生成指定的代理类,我们可以看到 ProxyGenerator.generateProxyClass方法传入了三个参数并且返回了一个byte数组,那我们再点进去看看这个方法里面干了什么 ? ? ? ? 点进去,是不是很眼熟?IO流?,Files.write(path, classFile); ? ? ? ? 看到这里基本就明白了吧,我们的代理类的class就是在这里被存入内存的。 ? ? ? ? 但是这就结束了吗?之前打的两个标记还没有被解答呢,那我们只好看看代理类里面的代码究竟是啥样的了,可是类是被加载在内存中的,我们怎么看呢?既然源码用了IO流,那么我们也模仿着这一个吧,先写个工具类。
? ? ? ? 我们可以看到,这个工具类里,我们也用了ProxyGenerator.generateProxyClass方法来获取byte[],然后输出到class文件内,但是这里我们只传了两个参数,第三个参数在重载时被自动赋值了49,我们来看看第三个参数是什么。
? ? ? ? ?可以看到这个参数的注释,是个标记为,OK那我们就先不传,看能不能实现,在我们的测试类中增加代码来创建这个class文件
? ? ? ? 执行完成后可以看到class文件已经被创建
? ? ? ? 最下面有个静态代码块, 在类加载的时候,给m0,m1,m2,m3进行了初始化,我们看右边,是不是就是反射创建的Method类?这里可以看到,equals,toString,doSth,hashCode四个方法都有被创建,那么我们再来回顾一下之前我们留下的标记 标记一:重写invoke方法的三个参数到底指什么 标记二:为什么代理对象的值是null ????????我们先来看重写invoke方法的三个参数到底是什么?我们先找到equals方法,这里可以看到,调用equals方法的时候,实际上调用的是 super.h.invoke(this, m1, new Object[]{var1}) ????????那么这个super.h是什么呢?我们可以看下h的类型,你会发现是InvocationHandler这个接口,也就是说,在调用这个代理类的equals时,实际上时调用了代理类的invoke方法,那我们再来看这个方法的参数。 ????????第一个参数是this,就是当前对象的引用,就是这个类实现的接口,就是这个User。 ? ? ? ? 第二个参数是m1,这个m1就是之前在静态代码块初始化的Method变量,实际就是通过反射获取到的被代理类的equals方法?。 ? ? ? ? 第三个参数是new Object[]{var1},这个val1可以看到就是调用equals方法时的传入参数,再将之封装成object数组。 ? ? ? ? 然后我们再回来看我们的增强类UserProxy的invoke方法 ? ? ? ? 是不是发现我们这个方法的返回是null?那么我们来做个试验,看能不能解决这个问题,我们把方法的返回值改为method.invoke中的返回值。
? ? ? ? 我们再来看下debug出来的对象是什么 ? ? ? ? ?是不是就可以看到我们这个userProxy对象的返回数据了?这个对象是$Proxy9,而打印的出的结果为什么是UserImpl呢?这个其实在上面也已经有了解答,因为代理对象的toString实际上是调用了被代理对象的toString,被代理对象的toString打印出来当然就是被代理对象的信息啦! ? ? ? ? 看到这里,对于动态代理的探究也就告一段落了,这边文章是基于我的基础和学习过程所写,算是我对动态代理的一份学习笔记,同时也希望能对在学习动态代理遇到问题的人寻求答案的人帮助有所。如果错误或是不详,请各位大佬帮忙指出。 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/17 20:25:20- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |