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

news2025/1/5 16:55:06

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

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

目录

源码分析 

过程

生成的代理类大致结构


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

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

      InvocationHandler最终存储在代理类实例的h字段中,这个字段是从Proxy类继承的。当调用代理对象的方法时,会调用这个h字段引用的InvocationHandler实例的invoke方法。这样就实现了方法调用的拦截和处理。 

源码分析 

public class Proxy implements java.io.Serializable {
    /** 
     * InvocationHandler对象,用于处理代理类的方法调用
     * 这个字段会被所有代理类继承,存储在代理类实例中
     */
    protected InvocationHandler h;
    
    /**
     * 代理类的构造函数,将InvocationHandler传递给代理类实例
     * 这个构造函数会被所有代理类调用
     */
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

    /**
     * 创建代理类实例的核心方法
     * loader: 类加载器,用于加载代理类
     * interfaces: 代理类需要实现的接口数组
     * h: 调用处理器,用于处理代理类的方法调用
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                        Class<?>[] interfaces,
                                        InvocationHandler h) {
        // 检查InvocationHandler是否为空
        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);

        try {
            // 检查权限
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            // 获取代理类的构造函数
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            
            // 如果构造函数是非public的,设置其可访问
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            
            // 创建代理类实例
            return cons.newInstance(new Object[]{h});
            
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            throw new InternalError(e.toString(), e);
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

    /**
     * 获取代理类的核心方法
     * 这个方法会调用ProxyClassFactory来生成代理类
     */
    private static Class<?> getProxyClass0(ClassLoader loader,
                                         Class<?>[] interfaces) {
        // 如果接口数量超过65535,抛出异常
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // 从缓存中获取代理类,如果不存在则生成
        return proxyClassCache.get(loader, interfaces);
    }
}

/**
 * 代理类工厂,负责生成代理类的字节码
 */
private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
    
    // 代理类名称前缀
    private static final String proxyClassNamePrefix = "$Proxy";

    // 原子计数器,用于生成唯一的代理类名
    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) {
            // 验证类加载器能否加载这个接口
            validateInterface(intf, loader, interfaceSet);
        }

        // 生成代理类的名称
        String proxyPkg = null;     // 包名
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

        // 检查所有非公共接口是否在同一个包中
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;  // 移除public标志
                String pkg = intf.getPackageName();
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }

        // 如果没有指定包名,默认使用com.sun.proxy
        if (proxyPkg == null) {
            proxyPkg = "com.sun.proxy";
        }

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

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

        try {
            // 加载代理类
            return defineClass0(loader, proxyName,
                              proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            throw new IllegalArgumentException(e.toString());
        }
    }
}

过程

JDK动态代理生成代理对象的过程可以分为以下几个关键步骤:

1.调用Proxy.newProxyInstance方法,传入类加载器、接口数组和InvocationHandler实例。这是整个代理创建过程的入口。

2.通过getProxyClass0方法获取代理类。这个方法会首先检查缓存中是否已经存在对应的代理类,如果不存在则创建新的代理类。

3.ProxyClassFactory负责生成代理类的字节码,其中会,验证接口的合法性,生成唯一的代理类名,确定代理类的包名,调用ProxyGenerator生成字节码

4. 获取代理类的构造函数,这个构造函数接收一个InvocationHandler参数。

5.使用构造函数创建代理类实例,将InvocationHandler传入。这个InvocationHandler会被存储在代理类的h字段中。

生成的代理类大致结构

public final class $Proxy0 extends Proxy implements UserService {
   
    
    // 静态方法对象,用于方法调用
    private static Method m1;
    private static Method m2;
    private static Method m3;
    
    static {
        try {
            // 初始化方法对象
            m1 = Class.forName("java.lang.Object")
                     .getMethod("equals", Object.class);
            m2 = Class.forName("com.example.UserService")
                     .getMethod("addUser", String.class);
            // ... 其他方法
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }
    
    // 构造函数,调用父类构造函数存储InvocationHandler
    public $Proxy0(InvocationHandler h) {
        super(h);
    }
    
    // 实现接口方法
    @Override
    public void addUser(String name) {
        try {
            // 调用InvocationHandler的invoke方法
            h.invoke(this, m2, new Object[]{name});
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
}

       InvocationHandler最终存储在代理类实例的h字段中,这个字段是从Proxy类继承的。当调用代理对象的方法时,会调用这个h字段引用的InvocationHandler实例的invoke方法。这样就实现了方法调用的拦截和处理。 

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

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

相关文章

STM32单片机芯片与内部57 SPI 数据手册 寄存器

目录 一、SPI寄存器 1、SPI控制寄存器 1(SPI_CR1)(I2S模式下不使用) 2、SPI控制寄存器 2(SPI_CR2) 3、SPI 状态寄存器(SPI_SR) 4、SPI 数据寄存器(SPI_DR) 5、SPI CRC多项式寄存器(SPI_CRCPR)(I2S模式下不使用&#xff09; 6、SPI Rx CRC寄存器(SPI_RXCRCR)(I2S模式下不…

vue设计与实现-框架设计

权衡的艺术 命令式和声明式 视图层框架通常分为命令式和声明式&#xff0c;各有优缺。jquery是一种命令式框架。命令式框架关注过程&#xff0c;而声明式框架关注结果。对于vue来说&#xff0c;过程被vue封装了&#xff0c;所以vue内部是命令式的&#xff0c;但vue暴露给用户…

CSDN充值、收费、会员

现在在CSDN上查阅资料经常碰到需要充值或变成会员才能继续阅读的情况&#xff0c;一直以为是博客作者为了赚钱而设置的&#xff0c;今天才知道原来是CSDN弄的&#xff0c;因为我在不登录的情况下查看自己的博文也需要充钱&#xff01;我可没有做过任何设置。

ElasticSearch7.10-分词器

文章目录 分词器1.字符过滤器1.介绍2.过滤html标签3.mappings过滤规则&#xff08;屏蔽非文明用语&#xff09;4.正则替换 2.自定义分词器1.代码2.查询 3.中文分词器1.下载ik分词器7.10.0版本&#xff08;跟es对应&#xff09;2.应用ik分词器1.进入插件目录下创建一个ik目录2.将…

Linux之ARM(MX6U)裸机篇----6.BSP工程管理实验

一&#xff0c;BSP工程管理 定义&#xff1a;是为了模块化整理代码&#xff0c;相同属性的文件存放在同一个目录下。 ①先mkdir多个文件夹er ②把共同.h文件转移到指定文件夹下 二&#xff0c;启动文件 .global _start /* 全局标号 *//** 描述&#xff1a; _start函数&am…

【kubernetes组件合集】深入解析Kubernetes组件之三:client-go

深入解析Kubernetes组件之三&#xff1a;client-go 目录 深入解析Kubernetes组件之三&#xff1a;client-go 引言 1. client-go简介 2. client-go的功能 2.1 资源操作 2.2 资源监听 2.3 认证和授权 2.4 错误处理和重试 2.5 扩展性和定制化 3. 使用client-go与Kubern…

【门铃工作原理】2021-12-25

缘由关于#门铃工作原理#的问题&#xff0c;如何解决&#xff1f;-嵌入式-CSDN问答 4 RST&#xff08;复位&#xff09;当此引脚接高电平时定时器工作&#xff0c;当此引脚接地时芯片复位&#xff0c;输出低电平。 按钮按下给电容器充电并相当与短路了R1改变了频率&#xff0c;按…

html+css+js网页设计 美食 美食3个页面(带js)

htmlcssjs网页设计 美食 美食3个页面(带js) 网页作品代码简单&#xff0c;可使用任意HTML辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 1&…

jenkins修改端口以及开机自启

修改Jenkins端口 方式一&#xff1a;通过配置文件修改&#xff08;以CentOS为例&#xff09; 找到配置文件&#xff1a;在CentOS系统中&#xff0c;通常可以在/etc/sysconfig/jenkins文件中修改Jenkins的配置。如果没有这个文件&#xff0c;也可以查看/etc/default/jenkins&…

vue-table-<td colspan=“2“>不生效

代码是2:1:4:1:4五分布局,效果却是如下: 因为这里的表格没有指定表格布局算法 /*设置表格布局算法*/ 2 table{ 3 table-layout:fixed; 4 } 这里需要了解table-layout属性值、定义和用法、固定表格布局、自动表格布局。 1定义和用法 tableLayout属性用来显示表格单元格、…

Windows系统提示ffmpeg.dll丢失怎么解决?

一、了解ffmpeg.dll文件 ffmpeg.dll是FFmpeg项目的一个动态链接库文件&#xff0c;FFmpeg是一个开源的多媒体处理框架&#xff0c;能够解码、编码、转码、混流、过滤和播放几乎所有已知格式的音频和视频文件。当Windows系统提示ffmpeg.dll丢失时&#xff0c;通常意味着某个需要…

QT:控件属性及常用控件(1)------核心控件及属性

一个图形化界面上的内容&#xff0c;不需要我们直接从零去实现 QT中已经提供了很多的内置控件&#xff1a; 按钮&#xff0c;文本框&#xff0c;单选按钮&#xff0c;复选按钮&#xff0c;下拉框等等。。。。。 文章目录 1.常用控件属性1.1 enabled1.2 geometry1.2.1 geometry…

使用Diffusion Models进行图像超分辩重建

Diffusion Models专栏文章汇总:入门与实战 前言:图像超分辨率重建是一个经典CV任务,其实LR(低分辨率)和 HR(高分辨率)图像仅在高频细节上存在差异。通过添加适当的噪声,LR 图像将变得与其 HR 对应图像无法区分。这篇博客介绍一种方式巧妙利用这个规律使用Diffusion Mod…

药片(药丸)和胶囊识别数据集,使用yolo,pasical voc xml, coco json格式标注,可识别药片和胶囊两种标签,2445张原始图片

药片(药丸)和胶囊识别数据集&#xff0c;使用yolo&#xff0c;pasical voc xml, coco json格式标注&#xff0c;可识别药片和胶囊两种标签&#xff0c;2445张原始图片 数据集分割 训练组80&#xff05; 1967图片 有效集13% 317图片 测试集7% 161图片 预处…

【生活】冬天如何选口罩(医用口罩,N95, KN95还是KP95?带不带呼吸阀门?带不带活性炭?)

&#x1f4a1;总结一下就是&#xff1a; 日常防护的话&#xff0c;医用口罩就可以啦。要是想长时间佩戴N95&#xff08;KN95&#xff09;口罩的话也可以. 在高风险环境&#xff08;像医院、疫情防控期间&#xff09;&#xff0c;一定要选不带呼吸阀门的N95口罩KN95&#xff09…

贪心算法概述

贪心算法总是作出当前看来最好的选择&#xff0c;是局部最优 可以使用贪心算法的问题一般具有两个重要的性质 贪心选择性质最优子结构性质 贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择来达到 其与动态规划的问题区别在于&#xff0c;动态规划算法通…

SD下载、安装、使用、卸载-Stable Diffusion整合包v4.10发布!

目录 前言概述 SD安装1、安装软件2、启动3、配置4、运行5、测试 导入SD模型【决定画风】常用模型下载安装模型 SD卸载SD文生图提示词提示词使用技巧提示词的高级使用技巧强调关键词 前言 我向来不喜欢搞一些没有用的概念&#xff0c;所以直接整理可能用到的东西。 sd简单的说…

【LeetCode】2506、统计相似字符串对的数目

【LeetCode】2506、统计相似字符串对的数目 文章目录 一、哈希表位运算1.1 哈希表位运算 二、多语言解法 一、哈希表位运算 1.1 哈希表位运算 每个字符串, 可用一个 int 表示. (每个字符 是 int 的一个位) 哈希表记录各 字符组合 出现的次数 步骤: 遇到一个字符串, 得到 ma…

【GO基础学习】gin的使用

文章目录 模版使用流程参数传递路由分组数据解析和绑定gin中间件 模版使用流程 package mainimport ("net/http""github.com/gin-gonic/gin" )func main() {// 1.创建路由r : gin.Default()// 2.绑定路由规则&#xff0c;执行的函数// gin.Context&#x…

溯源取证-手机取证-简单篇

好久没有写了&#xff0c;水一篇简单的 案例摘要&#xff1a; 我们目前正在调查一起谋杀案&#xff0c;目前已经获得了受害者的手机作为关键证据。在与证人和受害者核心圈子相关人员进行面谈后&#xff0c;您的目标是分析我们收集的信息&#xff0c;并努力追踪证据&#xff0…