JDK动态代理

JDK动态代理源码分析
4.1 JDK动态代理的实现
需要动态代理的接口

/**需要动态代理的接口
*/
public interface Movie {void player();void speak();

需要动态代理的接口的真实实现

/**需要动态代理接口的真实实现
*/
public class RealMovie implements Movie {@Override
public void player() {System.out.println("看个电影");
}@Override
public void speak() {System.out.println("说句话");
}
}

动态代理处理器

/*** 动态代理处理类*/public class MyInvocationHandler implements InvocationHandler {//需要动态代理接口的真实实现类private Object object;//通过构造方法去给需要动态代理接口的真实实现类赋值public MyInvocationHandler(Object object) {this.object = object;}/*** 对真实实现的目标方法进行增强* 当代理对象调用真实实现类的方法时,就会执行动态代理处理器中的该invoke方法** @param proxy  生成的代理对象* @param method 代理对象调用的方法* @param args   调用的方法中的参数* @return* @throws Throwable*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//方法增强System.out.println("卖爆米花");//object是真实实现,args是调用方法的参数//当代理对象调用真实实现的方法,那么这里就会将真实实现和方法参数传递过去,去调用真实实现的方法method.invoke(object,args);//方法增强System.out.println("扫地");return null;
}}

创建代理对象

public class DynamicProxyTest {

public static void main(String[] args) {// 保存生成的代理类的字节码文件//由于设置sun.misc.ProxyGenerator.saveGeneratedFiles 的值为true,所以代理类的字节码内容保存在了项目根目录下,文件名为$Proxy0.classSystem.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");//需要动态代理接口的真实实现RealMovie realMovie = new RealMovie();//动态代理处理类MyInvocationHandler handler = new MyInvocationHandler(realMovie);//获取动态代理对象//第一个参数:真实实现的类加载器//第二个参数:真实实现类它所实现的所有接口的数组//第三个参数:动态代理处理器Movie movie = (Movie) Proxy.newProxyInstance(realMovie.getClass().getClassLoader(),realMovie.getClass().getInterfaces(),handler);movie.player();}
}

结果

在这里插入图片描述

由于设置 sun.misc.ProxyGenerator.saveGeneratedFiles 的值为true,所以代理类的字节码内容保存在了项目根目录下,文件名为$Proxy0.class

在这里插入图片描述

生成的代理对象字节码文件

public final class $Proxy0 extends Proxy implements Movie {private static Method m1;private static Method m3;private static Method m2;private static Method m4;private static Method m0;static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m3 = Class.forName("com.eayon.dynamic.Movie").getMethod("player");m2 = Class.forName("java.lang.Object").getMethod("toString");m4 = Class.forName("com.eayon.dynamic.Movie").getMethod("speak");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}
}public $Proxy0(InvocationHandler var1) throws  {super(var1);
}/*** 重写被代理接口的方法* 因为生成的代理对象会实现被代理接口,所以我们在外部可以直接通过代理对象嗲偶哦那个被代理接口中的方法*/
public final void speak() throws  {try {//当外部通过代理对象调用被代理接口的方法时,其实是通过invocationHandler中的invoke()方法去调用的。//这个h就是invocationHandler(我们之前创建的MyInvocationHandler代理处理器)//this就是当前这个Proxy0代理对象//m4则具体要调用的方法super.h.invoke(this, m4, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}
}//与上面的speak方法同理,都是重写的被代理接口中的方法
public final void player() throws  {try {super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}
}public final String toString() throws  {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}
}public final int hashCode() throws  {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}
}public final boolean equals(Object var1) throws  {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}
}
}

代理对象的特点

1、代理类继承了Proxy类并且代理对象和真实实现一样都实现了要代理的接口

2、重写了equals、hashCode、toString

3、有一个静态代码块,通过反射或者代理类的所有方法

4、通过invoke执行代理类中的目标方法doSomething

4.2 源码分析

从上述代码中不难看出,创建代理对象的关键代码为:

//获取动态代理对象
//第一个参数:真实实现的类加载器
//第二个参数:真实实现类它所实现的所有接口的数组
//第三个参数:动态代理处理器
Movie movie = (Movie) Proxy.newProxyInstance(realMovie.getClass().getClassLoader(),realMovie.getClass().getInterfaces(),handler);

然后当执行如下代码的时候,也就是当代理对象调用真实实现的方法时,会自动跳转到动态代理处理器的invoke方法来进行调用。

movie.player();

这是为什么呢?

那其实归根结底都在Proxy.newProxyInstance()方法创建代理对象的源码中,我们一起来看看做了些什么

Proxy.newProxyInstance()

public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//判断代理处理器是否为空,为空则抛出空指针异常
Objects.requireNonNull(h);

//将真实实现类它所实现的所有接口的数据进行拷贝
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}//生成接口的代理对象的字节码文件(主要方法)
Class<?> cl = getProxyClass0(loader, intfs);/** 使用自定义的InvocationHandler作为参数,调用构造函数获取代理对象示例*/
try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}//通过代理对象的字节码文件获取代理对象的构造器final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}//通过代理对象的构造器调用newInstance()反射获取代理对象实例return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}
} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);
}
}

由上述代码我们发现,主要是通过getProxyClass0()方法去获取或者创建代理对象的字节码文件,通过代理对象字节码文件获取其构造器并通过反射生成代理对象实例。

那现在的重点就是如何获取或者创建代理对象的字节码文件,我们继续往下。

getProxyClass0(loader, intfs)

那其实真正生成代理对象字节码文件的是这个方法,他会传入真实实现的类加载器和他所实现的接口数组。

private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {//限制真实实现所实现的接口数量不能大于65535个if (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded");}// 首先从缓存中获取该接口对于的代理对象,如果有则返回,没有则通过ProxyClassFactory创建return proxyClassCache.get(loader, interfaces);
}

ProxyClassFactory

缓存中获取我们比较好理解,但是我们并没有在上述方法中发现proxyClassFactory

我们可以点击进入proxyClassCache.get()方法看看是如何从缓存中获取的

public V get(K key, P parameter) {
Objects.requireNonNull(parameter);

expungeStaleEntries();// 这里我们就理解成将真实实现的类加载器作为缓存key即可
Object cacheKey = CacheKey.valueOf(key, refQueue);// 从缓存中获取代理对象
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {ConcurrentMap<Object, Supplier<V>> oldValuesMap= map.putIfAbsent(cacheKey,valuesMap = new ConcurrentHashMap<>());if (oldValuesMap != null) {valuesMap = oldValuesMap;}
}// 缓存中不存在则根据subKeyFactory.apply(key, parameter)方法进行创建
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);.......省略无用代码........
}

subKeyFactory.apply(key, parameter)
我们点击进入apply方法发现其实是BiFunction接口,我们找到它的实现

在这里插入图片描述

此时我们发现,我们进入的这个apply方法所在的位置是Proxy类下ProxyClassFactory这个静态内部类中
所以当缓存中没有相应的代理对象,则会调用ProxyClassFactory类的apply方法来创建代理类。

ProxyClassFactory.apply()

private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 生成代理对象的字节码文件名的前缀,用于组装文件名
private static final String proxyClassNamePrefix = “$Proxy”;

// 生成代理对象字节码文件名的计数器,用于组装文件名(计数器默认从0开始)
private static final AtomicLong nextUniqueNumber = new AtomicLong();@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);for (Class<?> intf : interfaces) {//校验类加载器是否能通过接口名称加载该类Class<?> interfaceClass = null;try {interfaceClass = Class.forName(intf.getName(), false, loader);} catch (ClassNotFoundException e) {}if (interfaceClass != intf) {throw new IllegalArgumentException(intf + " is not visible from class loader");}//校验该类是否是接口类型if (!interfaceClass.isInterface()) {throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");}//校验接口是否重复if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName());}}String proxyPkg = null;     // 代理对象包名int accessFlags = Modifier.PUBLIC | Modifier.FINAL;/** 用于生成代理对象需要使用的包名* 非public接口,代理类的包名与接口的包名相同*/for (Class<?> intf : interfaces) {int flags = intf.getModifiers();if (!Modifier.isPublic(flags)) {accessFlags = Modifier.FINAL;String name = intf.getName();int n = name.lastIndexOf('.');String pkg = ((n == -1) ? "" : name.substring(0, n + 1));if (proxyPkg == null) {proxyPkg = pkg;} else if (!pkg.equals(proxyPkg)) {throw new IllegalArgumentException("non-public interfaces from different packages");}}}if (proxyPkg == null) {// 如果代理接口是public修饰的,则使用默认的com.sun.proxy package作为包名proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";}/** 为代理对象生成字节码文件名* 文件名格式:proxyName = 之前生成的包名 + $Proxy + 当前计数器的值(计数器默认从0开始)* 比如 proxyName = com.sun.proxy.$Proxy0*/long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;/** 真正生成代理对象字节码文件的地方*///生成代理对象字节码数组byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);try {// 将代理对象字节码数组生成字节码文件,并使用类加载器将代理对象的字节码文件加载到JVM内存中return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);} catch (ClassFormatError e) {throw new IllegalArgumentException(e.toString());}
}
}

我们可以看出它是通过ProxyGenerator.generateProxyClass()先生成代理对象字节码数组,

然后通过defineClass0()方法将代理对象的字节码数组生成字节码文件,并将该字节码通过类加载器加载到JVM中。

但是defineClass0()方法底层是通过native调用的C++,我们看不了,知道有这个事就行
在这里插入图片描述

ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags)

这个方法随便看看就行,不用过多理解

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);final byte[] var4 = var3.generateClassFile();// 是否要将生成代理对象的字节码文件保存到磁盘中// 该步骤也就是为什么之前我们在测试生成代理对象的时候使用System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");来将代理对象字节码文件保存下来if (saveGeneratedFiles) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {try {int var1 = var0.lastIndexOf(46);Path var2;if (var1 > 0) {Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));Files.createDirectories(var3);var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");} else {var2 = Paths.get(var0 + ".class");}Files.write(var2, var4, new OpenOption[0]);return null;} catch (IOException var4x) {throw new InternalError("I/O exception saving generated file: " + var4x);}}});}return var4;
}

4.3 总结

在这里插入图片描述
在这里插入图片描述

创建代理对象的核心方法就是Proxy.newProxyInstance()。该方法首先会调用getProxyClass0() 从缓存中获取或者创建代理对象字节码文件,拿到代理对象字节码文件后调用getConstructor()方法获取代理对象的构造器,最后通过cons.newInstance() 根据代理对象的构造器反射生成代理对象实例并返回。

我们回过头来看getProxyClass0()方法,该方法首先判断真实实现所实现的接口数量是否超限,没有超限则proxyClassCache.get()从缓存中获取代理实例。如果缓存中没有,则去创建代理对象。那么是如何创建的呢?首先会根据:包名 + $proxy0 + 当前计数器的值(默认从0开始) 生成代理对象的名称,其次根据名称和代理对象需要实现的被代理接口去生成代理对象字节码数组。拿到字节码数组之后,就可以通过调用的native方法去生成代理对象字节码文件,最后进行返回。

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

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

相关文章

HTTP/2 协议学习

HTTP/2 协议介绍 ​ HTTP/2 &#xff08;原名HTTP/2.0&#xff09;即超文本传输协议 2.0&#xff0c;是下一代HTTP协议。是由互联网工程任务组&#xff08;IETF&#xff09;的Hypertext Transfer Protocol Bis (httpbis)工作小组进行开发。是自1999年http1.1发布后的首个更新。…

Pytest和Unitest框架对比

在学到自动化的时候,很多同学都遇到了Pytest和Unitest框架,有的人是两个都学,但是学的不精只是知道分别怎么用.不了解两个区别是什么.有的是犹豫到底要学习那个框架.其实要做好自动化测试,是有必要了解不同框架之间的差异化的. Pytest 特点: Pytest采用了更简洁、更灵活的语法…

数据清洗!即插即用!异常值、缺失值、离群值处理、残差分析和孤立森林异常检测,确保数据清洗的全面性和准确性,MATLAB程序!

适用平台&#xff1a;Matlab2021版及以上 数据清洗是数据处理和分析中的一个关键步骤&#xff0c;特别是对于像风电场这样的大型、复杂数据集。清洗数据的目的是为了确保数据的准确性、一致性和完整性&#xff0c;从而提高数据分析的质量和可信度&#xff0c;是深度学习训练和…

BUCK电路布线规则、EMI分析

电源系列文章目录 本系列文章为博主在学习工作过程中的心得记录&#xff0c;欢迎评论区交流讨论。 BUCK电路工作原理、参数计算及工作模式分析BUCK电路布线规则、EMI分析电源电路中肖特基、续流二极管要求 目录 电源系列文章目录一、PCB布线规则1、输入电感与肖特基摆放2、输…

云原生容器技术入门:Docker、K8s技术的基本原理和用途

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《未来已来&#xff1a;云原生之旅》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、容器技术概述 1、什么是容器技术 2、容器技术的历史与发展 3…

QT事件处理系统之二:窗口部件的事件拦截,以及事件的传递顺序

1、案例说明 在父窗口中为selfLineEdit窗口安装事件过滤器,这样我们可以在父窗口中首先拦截来自于selfLineEdit本身产生的事件,并且决定该事件最终是否继续传递到selfLineEdit窗口本身。 2、关键代码 selfLineEdit.cpp #include "selfLineEdit.h" #include &l…

【树形dp 换根法 BFS】2581. 统计可能的树根数目

本文涉及知识点 CBFS算法 动态规划汇总 图论知识汇总 树形dp 换根法 BFS LeetCode 2581. 统计可能的树根数目 Alice 有一棵 n 个节点的树&#xff0c;节点编号为 0 到 n - 1 。树用一个长度为 n - 1 的二维整数数组 edges 表示&#xff0c;其中 edges[i] [ai, bi] &#xf…

将知乎专栏文章转换为 Markdown 文件保存到本地

一、参考内容 参考知乎文章代码 | 将知乎专栏文章转换为 Markdown 文件保存到本地&#xff0c;利用代码为GitHub&#xff1a;https://github.com/chenluda/zhihu-download。 二、步骤 1.首先安装包flask、flask-cors、markdownify 2. 运行app.py 3.在浏览器中打开链接&…

嵌入式学习——数据结构(哈希、排序)——day50

1. 查找二叉树、搜索二叉树、平衡二叉树 2. 哈希表——人的身份证——哈希函数 3. 哈希冲突、哈希矛盾 4. 哈希代码 4.1 创建哈希表 4.2 5. 算法设计 5.1 正确性 5.2 可读性&#xff08;高内聚、低耦合&#xff09; 5.3 健壮性 5.4 高效率&#xff08;时间复杂度&am…

Visual Studio开发环境搭建

原文&#xff1a;https://blog.c12th.cn/archives/25.html Visual Studio开发环境搭建 测试&#xff1a;笔记本原装操作系统&#xff1a;Windows 10 家庭中文版 资源分享链接&#xff1a;提取码&#xff1a;qbt2 注意事项&#xff1a;注意查看本地硬盘是否够用&#xff0c;建议…

2. 数据结构分析即索引库的crud

1. 数据库脚本 DROP TABLE IF EXISTS tb_hotel; CREATE TABLE tb_hotel (id bigint(0) NOT NULL,name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT COMMENT 酒店名称,address varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_090…

运营管理和服务支撑阶段

我前面的所有设备都部署好了&#xff0c;现在就需要运营管理和服务支撑 遇到问题了迅速解决&#xff0c;避免风险扩大 我们也可以给客户提供上面的服务&#xff0c;提高客户的预警能力&#xff0c;安全风险处理能力 我们不仅提供设备&#xff0c;还提供服务 我们公司成立了安…

【算法专题--链表】两两交换链表中的节点 -- 高频面试题(图文详解,小白一看就懂!!!)

目录 一、前言 二、题目描述 三、解题方法 ⭐双指针 -- 采用哨兵位头节点 &#x1f95d; 什么是哨兵位头节点&#xff1f; &#x1f34d; 解题思路 &#x1f34d; 案例图解 四、总结与提炼 五、共勉 一、前言 两两交换链表中的节点 这道题&#xff0c;可以说…

K8S - 在集群内反向代理外部资源 - headless service 的使用

在上一篇文章中 K8S - 理解ClusterIP - 集群内部service之间的反向代理和loadbalancer 介绍了 ClusterIP 的主要作用 : 在k8s 集群内部 代理 内部的多实例 service 但是ClusterIP 还有1个变种 -> 无头服务 (headless service) 它用于代理集群外部的 ip资源 当然代理外部资…

AI应用带你玩系列之SadTalker

前段时间我刷微信视频&#xff0c;我无意间点开了一个&#xff0c;画面缓缓展开&#xff0c;是一幅精致的水墨画&#xff0c;画中人物皆是古代装束&#xff0c;衣袂飘飘&#xff0c;仿佛能闻到墨香。然而&#xff0c;这宁静的画面突然被打破了&#xff0c;画中的人物开始动了起…

初识 SpringMVC,运行配置第一个Spring MVC 程序

1. 初识 SpringMVC&#xff0c;运行配置第一个Spring MVC 程序 文章目录 1. 初识 SpringMVC&#xff0c;运行配置第一个Spring MVC 程序1.1 什么是 MVC 2. Spring MVC 概述2.1 Spring MVC 的作用&#xff1a; 3. 运行配置第一个 Spring MVC 程序3.1 第一步&#xff1a;创建Mave…

鸿蒙开发系统基础能力:【@ohos.faultLogger (故障日志获取)】

故障日志获取 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import faultLogger from ohos.faultLoggerFaultType 故障类型枚举。 系统能力&#xff1a; 以下各项对应的系统能力…

利用MSSQL模拟提权

点击星标&#xff0c;即时接收最新推文 本文选自《内网安全攻防&#xff1a;红队之路》 扫描二维码五折购书 利用MSSQL模拟提权 在MS SQL数据库&#xff0c;可以使用EXECUTE AS语句&#xff0c;以其他用户的上下文执行SQL查询。需要注意的是只有明确授予模拟&#xff08;Impers…

vuex的深入学习[基于vuex3]----篇(二)

store对象的创建 store的传递图 创建语句索引 创建vuex的语句为new Vuex.Store({…})Vuex的入口文件是index.js,store是index.js导出的store类store类是store.js文件中定义的。 Store的构造函数constructor 判断vuex是否被注入&#xff0c;就是将vue挂载在window对象上&am…

Java | Leetcode Java题解之第169题多数元素

题目&#xff1a; 题解&#xff1a; class Solution {public int majorityElement(int[] nums) {int count 0;Integer candidate null;for (int num : nums) {if (count 0) {candidate num;}count (num candidate) ? 1 : -1;}return candidate;} }