JDK17源码分析Jdk动态代理底层原理

news2025/1/5 20:58:34

本文侧重分析JDK17中jdk动态代理的源码,若是想看JDK8源码分析可以看我的这一篇文章 JDK8源码分析Jdk动态代理底层原理-CSDN博客

两者之间有着略微的差别,JDK17在JDK8上改进了不少

目录

JDK 17的动态代理源码

 核心入口方法 newProxyInstance

 获取代理类构造函数 getProxyConstructor 

单接口和多接口的区别

 缓存的实现和使用

性能优化总结

ProxyBuilder类的核心实现

  代理类生成过程 defineProxyClass

 代理类的结构


本文侧重分析JDK17中jdk动态代理的源码,若是想看JDK8源码分析可以看我的这一篇文章 JDK8源码分析Jdk动态代理底层原理-CSDN博客

两者之间有着略微的差别,JDK17在JDK8上改进了不少

JDK 17的动态代理源码

 核心入口方法 newProxyInstance

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                    Class<?>[] interfaces,
                                    InvocationHandler h) {
    Objects.requireNonNull(h);

    // 获取调用者类,用于权限检查
    final Class<?> caller = System.getSecurityManager() == null
                              ? null
                              : Reflection.getCallerClass();

    // 获取代理类的构造函数
    Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

    // 创建代理实例
    return newProxyInstance(caller, cons, h);
}

这是创建代理实例的主要入口,他主要就是检查InvocationHandler是否为空,然后获取调用者类信息(用于安全检查),获取代理类的构造函数(这个阶段就,创建并返回代理实例 

 获取代理类构造函数 getProxyConstructor 

private static Constructor<?> getProxyConstructor(Class<?> caller,
                                                ClassLoader loader,
                                                Class<?>... interfaces) {
    // 优化:如果只有一个接口,使用简化的缓存查找
    if (interfaces.length == 1) {
        Class<?> intf = interfaces[0];
        if (caller != null) {
            checkProxyAccess(caller, loader, intf);
        }
        return proxyCache.sub(intf).computeIfAbsent(
            loader,
            (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
        );
    } else {
        // 多接口情况
        final Class<?>[] intfsArray = interfaces.clone();
        if (caller != null) {
            checkProxyAccess(caller, loader, intfsArray);
        }
        final List<Class<?>> intfs = Arrays.asList(intfsArray);
        return proxyCache.sub(intfs).computeIfAbsent(
            loader,
            (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
        );
    }
}

这个方法负责获取代理类的构造函数:对单接口情况做了特殊优化,还使用了使用缓存机制避免重复生成代理类,最后通过ProxyBuilder来实际构建代理类,这个ProxyBuilder是Proxy类的静态内部类。

先了解一下Proxy类采用的缓存机制和单接口优化

单接口和多接口的区别
// 单接口情况
Class<?> intf = interfaces[0];
return proxyCache.sub(intf).computeIfAbsent(...)

// 多接口情况
final Class<?>[] intfsArray = interfaces.clone();
final List<Class<?>> intfs = Arrays.asList(intfsArray);
return proxyCache.sub(intfs).computeIfAbsent(...)

内存优化:单接口直接使用接口Class对象作为缓存key,而多接口需要克隆数组并转换为List,会产生额外的对象创建

缓存key的复杂度:单接口的key就是一个Class对象,更简单高效,而多接口的key是List<Class<?>>,需要进行equals比较时要遍历所有元素

查找效率:单接口的缓存查找更快,因为只需要比较一个Class对象,而多接口需要比较整个接口列表,耗时更多

 缓存的实现和使用

再回到源码中

// Proxy类中的缓存字段
private static final ClassLoaderValue<Constructor<?>> proxyCache = 
    new ClassLoaderValue<>();

这个就算是个map存储已经生成过的代理类。

 缓存查找的过程:

// 单接口
proxyCache.sub(intf)  // 使用接口Class作为key创建子缓存
         .computeIfAbsent(  // 在子缓存中查找或计算
             loader,        // 使用ClassLoader作为key
             (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()  // 缓存未命中时的计算逻辑
         )

// 多接口
proxyCache.sub(intfs)  // 使用接口List作为key创建子缓存
         .computeIfAbsent(...)  // 同上

 首先检查缓存是否存在,如果存在,直接返回缓存的Constructor,如果不存在,则创建新的代理类,创建ProxyBuilder实例,生成代理类,获取构造函数,将构造函数存入缓存

性能优化总结

空间优化:单接口不需要创建额外的数组和List对象,缓存机制避免重复生成代理类

时间优化:单接口的缓存查找更快,避免重复的字节码生成,使用computeIfAbsent保证线程安全的同时提高并发性能

缓存命中率:相同接口组合的代理类只会生成一次,基于ClassLoader的分层缓存,避免类加载器隔离导致的缓存失效

ProxyBuilder类的核心实现

private static final class ProxyBuilder {
    // 代理类名称前缀
    private static final String proxyClassNamePrefix = "$Proxy";
    
    // 用于生成唯一代理类名的计数器
    private static final AtomicLong nextUniqueNumber = new AtomicLong();
    
    private final List<Class<?>> interfaces;
    private final Module module;

    ProxyBuilder(ClassLoader loader, List<Class<?>> interfaces) {
        // 验证模块系统是否初始化完成
        if (!VM.isModuleSystemInited()) {
            throw new InternalError("Proxy is not supported until "
                    + "module system is fully initialized");
        }

        // 验证接口数量不超过限制
        if (interfaces.size() > 65535) {
            throw new IllegalArgumentException("interface limit exceeded: "
                    + interfaces.size());
        }

        // 获取所有引用的类型
        Set<Class<?>> refTypes = referencedTypes(loader, interfaces);

        // 验证代理接口
        validateProxyInterfaces(loader, interfaces, refTypes);

        this.interfaces = interfaces;
        // 确定代理类所属的模块
        this.module = mapToModule(loader, interfaces, refTypes);
    }
}

ProxyBuilder负责构建代理类,验证接口数量限制

  代理类生成过程 defineProxyClass

private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
    String proxyPkg = null;     // 代理类的包名
    int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
    boolean nonExported = false;

    // 确定代理类的包名和访问权限
    for (Class<?> intf : interfaces) {
        int flags = intf.getModifiers();
        if (!Modifier.isPublic(flags)) {
            accessFlags = Modifier.FINAL;  // 非公共的,只需要final
            String pkg = intf.getPackageName();
            if (proxyPkg == null) {
                proxyPkg = pkg;
            } else if (!pkg.equals(proxyPkg)) {
                throw new IllegalArgumentException(
                    "non-public interfaces from different packages");
            }
        } else {
            if (!intf.getModule().isExported(intf.getPackageName())) {
                nonExported = true;
            }
        }
    }

    // 生成代理类名称
    long num = nextUniqueNumber.getAndIncrement();
    String proxyName = proxyPkg.isEmpty()
                      ? proxyClassNamePrefix + num
                      : proxyPkg + "." + proxyClassNamePrefix + num;

    // 生成代理类的字节码
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
        loader, proxyName, interfaces, accessFlags);

    // 定义代理类
    return JLA.defineClass(loader, proxyName, proxyClassFile,
                          null, "__dynamic_proxy__");
}

代理类生成先确定代理类的包名和访问权限,生成唯一的代理类名,再生成代理类的字节码,最后通过类加载器定义代理类

 代理类的结构

public final class $Proxy0 extends Proxy implements UserService {
    private static final Method m0;
    private static final Method m1;
    
    static {
        try {
            m0 = Class.forName("java.lang.Object")
                     .getMethod("hashCode");
            m1 = Class.forName("com.example.UserService")
                     .getMethod("addUser", String.class);
        } catch (NoSuchMethodException|ClassNotFoundException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

    public $Proxy0(InvocationHandler h) {
        super(h);
    }

    public final void addUser(String param0) {
        try {
            super.h.invoke(this, m1, new Object[]{param0});
        } catch (RuntimeException|Error e) {
            throw e;
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
}

JDK 17的动态代理实现相比JDK8版本的主要改进:

  • 增加了模块系统支持
  • 改进了缓存机制
  • 增强了安全性检查
  • 优化了性能
  • 提供了更好的错误处理

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

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

相关文章

【网络协议】开放式最短路径优先协议OSPF详解(一)

OSPF 是为取代 RIP 而开发的一种无类别的链路状态路由协议&#xff0c;它通过使用区域划分以实现更好的可扩展性。 文章目录 链路状态路由协议OSPF 的工作原理OSPF 数据包类型Dijkstra算法、管理距离与度量值OSPF的管理距离OSPF的度量值 链路状态路由协议的优势拓扑结构路由器O…

vim 的基础使用

目录 一&#xff1a;vim 介绍二&#xff1a;vim 特点三&#xff1a;vim 配置四&#xff1a;vim 使用1、vim 语法格式2、vim 普通模式&#xff08;1&#xff09;保存退出&#xff08;2&#xff09;光标跳转&#xff08;3&#xff09;文本删除&#xff08;4&#xff09;文本查找&…

为什么深度学习和神经网络要使用 GPU?

为什么深度学习和神经网络要使用 GPU&#xff1f; 本篇文章的目标是帮助初学者了解 CUDA 是什么&#xff0c;以及它如何与 PyTorch 配合使用&#xff0c;更重要的是&#xff0c;我们为何在神经网络编程中使用 GPU。 图形处理单元 (GPU) 要了解 CUDA&#xff0c;我们需要对图…

工厂模式与抽象工厂模式在Unity中的实际应用案例

一、实验目的 实践工厂模式和抽象工厂模式的实际应用。 创建一个小型的游戏场景&#xff0c;通过应用这些设计模式提升游戏的趣味性和可扩展性。 掌握在复杂场景中管理和使用不同类型的对象。 比较在实际游戏开发中不同设计模式的实际效果和应用场景。 学习如何进行简单的性…

jrc水体分类对水体二值掩码修正

使用deepwatermap生成的水体二值掩码中有部分区域由于被云挡住无法识别&#xff0c;造成水体不连续是使用jrc离线数据进行修正&#xff0c;jrc数据下载连接如下&#xff1a;https://global-surface-water.appspot.com/download 选择指定区域的数据集合下载如图&#xff1a; 使…

计算机网络 (20)高速以太网

一、发展背景 随着计算机技术和网络应用的不断发展&#xff0c;传统的以太网速率已逐渐无法满足日益增长的带宽需求。因此&#xff0c;高速以太网应运而生&#xff0c;它以提高数据传输速率为主要目标&#xff0c;不断推动着以太网技术的发展。 二、技术特点 高速传输&#xff…

基于SpringBoot的校园二手交易平台的设计与实现(源码+SQL+LW+部署讲解)

文章目录 摘 要1. 第1章 选题背景及研究意义1.1 选题背景1.2 研究意义1.3 论文结构安排 2. 第2章 相关开发技术2.1 前端技术2.2 后端技术2.3 数据库技术 3. 第3章 可行性及需求分析3.1 可行性分析3.2 系统需求分析 4. 第4章 系统概要设计4.1 系统功能模块设计4.2 数据库设计 5.…

2024年中国新能源汽车用车发展怎么样 PaperGPT(二)

用车趋势深入分析 接上文&#xff0c;2024年中国新能源汽车用车发展怎么样 PaperGPT&#xff08;一&#xff09;-CSDN博客本文将继续深入探讨新能源汽车的用车强度、充电行为以及充电设施的现状。 用车强度 月均行驶里程&#xff1a;2024年纯电车辆月均行驶超过1500公里&…

antd-vue - - - - - a-date-picker限制选择范围

antd-vue - - - - - a-date-picker限制选择范围 1. 效果展示2. 代码展示 1. 效果展示 如图&#xff1a;限制选择范围为 今年 & 去年 的 月份. 2. 代码展示 <template><a-date-picker:disabledDate"disabledDate"picker"month"/> &l…

滑动窗口、流量控制和拥塞控制

1. 确认应答机制 确认应答机制是计算机网络中&#xff0c;用于确保数据可靠传输的一种方法。 它通过发送 ACK 数据段来通知对方&#xff0c;每一个 ACK 数据段都有一个确认序号&#xff0c;表明&#xff1a; 确认序号之前的所有数据都已被接收&#xff0c;接下来从确认序号开…

TCP粘/拆包----自定义消息协议

今天是2024年12月31日&#xff0c;今年的最后一天&#xff0c;希望所有的努力在新的一年会有回报。❀ 无路可退&#xff0c;放弃很难&#xff0c;坚持很酷 TCP传输 是一种面向二进制的&#xff0c;流的传输。在传输过程中最大的问题是消息之间的边界不明确。而在服务端主要的…

前端,npm install安装依赖卡在sill idealTree buildDeps(设置淘宝依赖)

输入npm i后&#xff0c;一直卡在sill idealTree buildDeps&#xff0c;一动不动 cnpm可以安装成功&#xff0c;但使用cnpm不会生成package-lock.json文件 设置淘宝依赖&#xff0c;依然卡住&#xff0c;挂梯子也不行 解决方法&#xff1a; // 取消ssl验证 set strict-ssl …

【有作图代码】Highway Network与ResNet:skip connection如何解决深层网络欠拟合问题

【有作图代码】Highway Network与ResNet&#xff1a;skip connection如何解决深层网络欠拟合问题 关键词&#xff1a; #Highway Network #ResNet #skip connection #深层网络 #欠拟合问题 具体实例与推演 假设我们有一个深层神经网络&#xff0c;其层数为L&#xff0c;每一…

目标检测入门指南:从原理到实践

目录 1. 数据准备与预处理 2. 模型架构设计 2.1 特征提取网络原理 2.2 区域提议网络(RPN)原理 2.3 特征金字塔网络(FPN)原理 2.4 边界框回归原理 2.5 非极大值抑制(NMS)原理 2.6 多尺度训练与测试原理 2.7 损失函数设计原理 3. 损失函数设计 4. 训练策略优化 5. 后…

搭建开源版Ceph分布式存储

系统&#xff1a;Rocky8.6 三台2H4G 三块10G的硬盘的虚拟机 node1 192.168.2.101 node2 192.168.2.102 node3 192.168.2.103 三台虚拟机环境准备 1、配置主机名和IP的映射关系 2、关闭selinux和firewalld防火墙 3、配置时间同步且所有节点chronyd服务开机自启 1、配置主机名和…

租用服务器还是服务器托管:哪种方案更适合您?

随着企业对网络服务质量要求的不断提高&#xff0c;租用服务器和服务器托管是两种常见的选择&#xff0c;各自具备独特的优势和适用场景。这篇文章将从多个维度对这两种方案进行详细分析&#xff0c;帮助大家进行对比选择。 租用服务器的优劣势分析 优点 无需大额初始投入 租用…

LDD3学习6--Scull的变种

1 整体介绍 之前在LDD3学习1里面就提过scull的变种&#xff0c;LDD学习1--启程-CSDN博客&#xff0c;大概的变种有这些&#xff1a; 名称全名说明对应章节scullSimple Character Utility for Loading Localities基础版本3scullcScull with Slab cache使用基于slab高速缓存8.2.…

设计模式の状态策略责任链模式

文章目录 前言一、状态模式二、策略模式三、责任链模式 前言 本篇是关于设计模式中的状态模式、策略模式、以及责任链模式的学习笔记。 一、状态模式 状态模式是一种行为设计模式&#xff0c;核心思想在于&#xff0c;使某个对象在其内部状态改变时&#xff0c;改变该对象的行为…

【网络协议】路由信息协议 (RIP)

未经许可&#xff0c;不得转载。 路由信息协议&#xff08;Routing Information Protocol&#xff0c;简称 RIP&#xff09;是一种使用跳数&#xff08;hop count&#xff09;作为路由度量标准的路由协议&#xff0c;用于确定源网络和目标网络之间的最佳路径。 文章目录 什么是…

linux下安装达梦数据库v8详解

目录 操作系统、数据库 1、下载达梦数据库 2、安装前准备 2.1、建立数据库用户和组 2.2、修改文件打开最大数 2.3、挂载镜像 2.4、新建安装目录 3、数据库安装 4、配置环境变量 5、初始化数据库实例 6、注册服务 7、使用数据库 8、卸载数据库 9、多实例管理 10、…