【源码解析】JDK 动态代理实现

news2024/11/24 9:50:37

基本流程

主方法

在该方法中,我们呈现了在日常使用 JDK 动态代理机制的方法。

public class VehicleDynamicProxy {  
    /**  
     * 被代理对象  
     */  
    public Vehicle targetVehicle;  
  
    public VehicleDynamicProxy(Vehicle targetVehicle) {  
        this.targetVehicle = targetVehicle;  
    }  
  
    public Vehicle getProxy() {  
        //获取类的加载器  
        ClassLoader classLoader = targetVehicle.getClass().getClassLoader();  
        //获取类的接口数组  
        Class<?>[] interfaces = targetVehicle.getClass().getInterfaces();  
        //调用函数,用来的调用方法  
        InvocationHandler invocationHandler = new InvocationHandler() {  
            @Override  
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
                Object result = null;  
                try {  
                    System.out.println("前置通知 before");  
                    //调用被代理对象的方法  
                    result = method.invoke(targetVehicle, args);  
                    System.out.println("返回通知 afterReturning");  
                } catch (Exception e) {  
                    //异常通知 AfterThrowing                    throw new RuntimeException(e);  
                } finally {  
                    //最终通知 After                }  
                return result;  
            }  
        };  
        //此时的proxy对象是实现了Vehicle接口,继承了Proxy类的代理对象  
        Vehicle proxy = (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);  
        return proxy;  
    }  
}

解读:

  • 我们应该特别注意到 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);  
  
    final Constructor<?> cons = cl.getConstructor(constructorParams);  
    
    return cons.newInstance(new Object[]{h});  
}

解读:

  • 我们先校验了相关权限,然后通过 getProxyClass0(loader, intfs); 生成代理类的 Class 对象(此时,我们可以通过这个对象生成代理类实例),最后我们通过构造器的方式生成代理类实例;
  • 毫无疑问,getProxyClass0 方法是最重要的。这也是我们探索的主要阵地;

getProxyClass0 ()

private static Class<?> getProxyClass0(ClassLoader loader,  
                                       Class<?>... interfaces) {  
    if (interfaces.length > 65535) {  
        throw new IllegalArgumentException("interface limit exceeded");  
    }  
  
    // If the proxy class defined by the given loader implementing  
    // the given interfaces exists, this will simply return the cached copy;    // otherwise, it will create the proxy class via the ProxyClassFactory    
    return proxyClassCache.get(loader, interfaces);  
}

解读:

  • 从源码注释我们就可以知道,倘若类加载器和接口数组都已经存在,那么就会通过 ProxyClassFactory 创建一个代理类的 Class 对象;
  • 很明显,我们应该看 ProxyClassFactory 类;

ProxyClassFactory

该类是 Proxy 类的一个静态内部工厂类。如下图所示:

我们还可以发现该类只有一个方法apply(),所以只有这个方法才可以生成我们的代理类:

接下来是对于ProxyClassFactory.apply()的解读,同样,我们会精简出核心的逻辑:

@Override  
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {  
  
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);  
        // package to define proxy class in
        String proxyPkg = null;       
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;  
        if (proxyPkg == null) {  
            // if no non-public proxy interfaces, use com.sun.proxy package  
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";  
        }  
  
	    long num = nextUniqueNumber.getAndIncrement();  
        String proxyName = proxyPkg + proxyClassNamePrefix + num;  
  
        /*  
         * Generate the specified proxy class.         */         
         */
	    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(  
            proxyName, interfaces, accessFlags);  
       
        return defineClass0(loader, proxyName,  
                                proxyClassFile, 0, proxyClassFile.length);  
       
    }  
}

解读:

  • 首先我们先生成一个代理类的名字 proxyNameproxyClassNamePrefix 的值就为我们喜闻乐见的:

    同时也为我们指示了包名:

  • 然后,我们通过 generateProxyClass 方法生成了代理类的字节码,可以看到这里特别点名要使用到我们提供的接口数组

  • 最后,调用本地方法 defineClass0 生成 Class 对象:

  • 显然,我们应该继续追 generateProxyClass 方法的源码;

ProxyGenerator. generateProxyClass ()

参数描述:

  • var0:代理类的名字;
  • var1:接口数组;
  • var2:权限校验参数(不用管);
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {  
    ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);  
    final byte[] var4 = var3.generateClassFile();  
    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;  
}

解读:

  • 其实该方法只用看前两行。通过 var3.generateClassFile() 生成一个 byte 数组 var4,该数组就是代理类对应的 Class 对象的字节表示,该数组会通过本地方法 defineClass0 生成 Class 对象;
  • saveGeneratedFiles 属性主要是决定程序是否将生成的动态代理对象保存到磁盘上,这个属性将在我们剖析生成的动态代理类结构发挥极大的作用;

总结

通过上述对于源码的解析,我们可以发现,动态代理对象的创建涉及到底层本地方法,也就是说,动态代理对象是通过我们提供的类信息由 JVM 虚拟机自动创建的。这就是动态代理区别于其他代理方式的根本不同。
动态代理方式提供了更加的灵活的选择。


不过,我们还没有解决为什么在生成代理对象的时候要给出接口的问题。
我们将通过解析运行阶段生成的代理对象来分析该问题。

代理对象详解

调试理解

我们先通过调试来看看这个对象究竟是什么:

解读:

  • 我们的动态代理类原来叫做 $Proxy0,我们接下来直接通过一些手段拿到该动态代理类的字节码,然后进行反编译;

拿到动态代理对象

注意到,ProxyGenerator.generateProxyClass() 方式中的 saveGeneratedFiles 参数,我们通过这个参数来让动态代理对象写入到磁盘中。
有两种方法将该参数调整为 true。
设计 vm 参数:

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

在生成代理对象前调用如下代码:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

于是,我们就可以在项目主目录下,拿到 JVM 自动生成的动态代理类:

代理对象剖析

反编译后如下,我们在该类中省略了无关的方法实现,例如 hashcodetoString,只保留了被代理对象实现的方法:

package com.sun.proxy;

import com.yelanyanyu.dynamicProxy.Vehicle;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Vehicle {
    private static Method m3;
    private static Method m4;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final void run() 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 fly(int var1) throws  {
        try {
            return (String)super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    static {
        try {
            m3 = Class.forName("com.yelanyanyu.dynamicProxy.Vehicle").getMethod("run");
            m4 = Class.forName("com.yelanyanyu.dynamicProxy.Vehicle").getMethod("fly", Integer.TYPE);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

解读:

  • 从方法的描述 $Proxy0 extends Proxy implements Vehicle 可以看出,该代理类的类名就是我们之前分析的 $Proxy0
  • 该类继承原来的 Proxy 类,实现我们提供的接口;
  • 特别注意return (String)super.h.invoke(this, m4, new Object[]{var1});,我们可以再次印证我在前一篇文章中得出的结论,代理对象的方法调用是通过转发到 InvocationHandler 的 invoke 来实现的;

为什么一定是接口

现在我们就可以来解释解释这个问题了。原因主要有:

  • 代理对象是动态创建的,也就是说,程序员刚开始并不知道其类名,也不能在编译阶段使用这个代理对象。所以,我们只能使用 OOP 原则中的向下转型,用 $Proxy0 的父类或者其实现的接口来接收,但是 $Proxy0 的父类已经是 Proxy 类了(Proxy 类中没有对应的方法);由于 Java 是单继承的,所以只能用实现接口的方式,来让接口接受 JVM 创建的动态代理对象,如下:
Vehicle proxy = (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

解读:

  • 我们不可能写成 $Proxy0 proxy = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

  • 或者 Proxy proxy = (Proxy) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

  • 另一个原因则是默认了对象采用了合适的模板设计模式:类的绝大部分方法都在接口中有实现,代理对象可以使用到被代理对象的绝大部分方法。

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

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

相关文章

注册中心要集成 SpringCloud实现负载均衡,需要哪些接口和规范?

前几天有个大兄弟问了我一个问题&#xff0c;注册中心要集成SpringCloud&#xff0c;想实现SpringCloud的负载均衡&#xff0c;需要实现哪些接口和规范。 既然这个兄弟问到我了&#xff0c;而我又刚好知道&#xff0c;这不得好好写一篇文章来回答这个问题&#xff0c;虽然在后面…

Omniverse Extensions Command 写在其他.py 文件内 导致找不到Command 无法运行 不生效 的解决方案

提要&#xff1a; Command扩展不在extension.py内 例如我的Command&#xff1a;ScatterCreatePointInstancerCommandLJ 在 commands.py 内 然后使用时报错&#xff1a; 2023-04-10 18:16:11 [Error] [omni.kit.commands.command] Cant execute command: "ScatterCreat…

基于springboot和ajax的简单项目 05 日志界面的查找功能

01.这个是基于input标签的查找功能。 02.修改更新table标签的函数&#xff0c;把username这个参数&#xff0c;加到getjson函数的params中&#xff0c;实现依靠username来查询数据。 var username$("#searchNameId").val();//如下语句的含义是什么&#xff1f;动态在…

STM32F4_PWM输出详解

目录 1. PWM简介 2. PWM原理 3. 定时器PWM输出比较 4. 定时器PWM捕获/比较通道 5. PWM输出相关寄存器 5.1 捕获/比较模式寄存器 TIMx_CCMR1 5.2 捕获/比较使能寄存器 TIMx_CCER 5.3 捕获/比较寄存器 TIMx_CCR1~4 5.4 刹车(断路)和死区寄存器 TIMx_BDTR 6. 库函数配置…

【Python_Scrapy学习笔记(一)】Scrapy框架简介

Scrapy框架简介 前言 Scrapy 框架是一个用 python 实现的为了爬取网站数据、提取数据的应用框架&#xff0c;使用 Twisted 异步网络库来处理网络通讯&#xff0c;可以高效的完成数据爬取。本文主要介绍 Scrapy 框架的构成与工作原理。 正文 1、Scrapy安装 Windows安装&…

【Unity UPR】造个获取深度法线纹理的轮子

描边需要深度法线纹理的加持&#xff0c;效果才能达到最好&#xff0c;但URP下很多版本不支持直接获取_CameraNormalsTexture&#xff0c;而我本人也尝试了一下在12.1.7下偷懒直接拿SSAO里的Depth Normal图&#xff0c; 虽然也能实现吧&#xff0c;但是需要打开SSAO的同时&…

商务接待广州考斯特商务租车详解!

进入四月份以来&#xff0c;全国各个地区都有很多商务活动举办&#xff0c;广州也不例外&#xff0c;广州很多地区都有商务活动的需求。因此不少主办方都需要商务租车来接待客户&#xff0c;而丰田考斯特是市面上常见的一款高端中巴车&#xff0c;主要是因为考斯特的可靠性、安…

【软件设计师13】数据库设计

数据库设计 1. 数据库设计过程 2. E-R模型 3. E-R图向关系模型的转换 例如一对一联系&#xff0c;可以将联系单独做为关系模式&#xff0c;也可以存放到任意一个实体中 而一对多要合并只能合并到多这边&#xff0c;不能存放到1 多对多则联系必须单独转成一个关系模式 4. 案…

赛狐ERP | 亚马逊选品方法与策略详解:如何挑选最优质的产品?

亚马逊作为全球电商巨头&#xff0c;其产品种类之丰富也是无人能及。然而&#xff0c;在如此繁杂的商品体系下&#xff0c;如何选品成为了摆在商家面前的一道难题。本文将从亚马逊选品的目标、方法、策略三个方面进行详细介绍。 一、选品的目标 在进行选择之前&#xff0c;必…

【C语言】位运算 {位运算的应用 :关闭位,判断位,打开位,转置位;位域}

一、基础 参与位运算的对象只能是整型数据(int, unsigned, char)&#xff0c;不能为实型 移位操作符 按位左移n位表示&#xff1a;原数*2^n按位右移n位表示&#xff1a;原数/2^n&#xff08;整除&#xff09;上述运算只适用于左右移位时被溢出舍弃的位不包含1的情况 二、位运…

7.redis-集群

目录 1. 概念 2. 三主三从redis集群配置 3. redis集群读写 4. 主从扩展案例 5.主从降容案例 6.用到的命令 1. 概念 1).分片: 集群中的每个redis实例都被认为是整个数据的一个分片&#xff0c;官方建议是最大1000个 2).槽位: redis集群有16384个哈希槽&#xff0c;每个key…

Mac平台上有哪些好用的常用软件?

我大概分几类给你介绍一下吧。 一、办公类 1.微软的office系列&#xff0c;在mac平台也有office的全家桶&#xff0c;习惯用微软office的也可以安装。 2.wps office&#xff0c;wps可以说是国产最好用的office软件&#xff0c;最重要的是wps可以跨平台&#xff0c;并且云文档…

C/C++程序设计——static关键字

一、修饰局部变量 &#xff08;1&#xff09;称为静态局部变量&#xff0c;改变局部变量的生命周期&#xff0c;生命周期由局部变为全局。 &#xff08;2&#xff09;作用域不发生改变。 &#xff08;3&#xff09;静态局部变量只能被初始化一次。 本质&#xff1a; 改变了局…

获取UNIX系统时间

① 基本认识 UNIX系统时间主要分为两种&#xff1a; 日历时间 和 进程时间 ② 日历时间 该时间是自协调时间时间 1970年1月1日 00:00:00这个特定时间来计算累积的秒数。&#xff08;称为UTC 格林尼治标准时间&#xff09; 时间值是存放在系统类型time_t里面. ③ 进程时间 也称为…

redis哨兵模式配置(配置文件等)

Redis-Sentinel机制主要用三个功能&#xff1a; (1)监控&#xff1a;不停监控Redis主从节点是否安装预期运行 (2)提醒&#xff1a;如果Redis运行出现问题可以 按照配置文件中的配置项 通知客户端或者集群管理员 (3)自动故障转移&#xff1a;当主节点下线之后&#xff0c;哨兵…

OpenGL 简介

OpenGL 简介 GPU 接口规范 对于刚接触 OpenGL 的初学者,常常会有这样一个疑问: OpenGL 的源码在哪里,如何编译? 然而实际上 OpenGL 并不是一个软件实现,更多的是一个标准协议; OpenGL 更像是一种显卡驱动标准,由各个硬件厂家适配,各个硬件厂商根据 OpenGL 接口规范编撰对应的…

【系统集成项目管理工程师】项目进度管理

&#x1f4a5;十大知识领域&#xff1a;项目进度管理 主要考计算题 项目进度管理包括以下 7 个过程: 规划进度管理过程定义活动过程排列活动顺序过程估算活动资源过程估算活动持续时间过程制定进度计划过程控制进度过程 一、规划进度管理过程 制定政策、程序和文档以管理项目进…

亲测:腾讯云轻量应用服务器性能如何?

腾讯云轻量应用服务器性能评测&#xff0c;轻量服务器CPU主频、处理器型号、公网带宽、月流量、Ping值测速、磁盘IO读写及使用限制&#xff0c;轻量应用服务器CPU内存性能和标准型云服务器CVM处于同一水准&#xff0c;所以大家不要担心轻量应用服务器的性能&#xff0c;腾讯云百…

【CSS】13.页面切图和布局实现

页面切图和布局实现 1. 浮动布局 1.1 页面布局 LOGO 部分 NAV 布局 LEFT - SIDEBAR&#xff1a;左边栏布局 CONTENT&#xff1a;内容布局 RIGHT - SIDEBAR&#xff1a;右边栏布局 1.2 流式布局 块的默认布局叫做流式布局 但流式布局并不能满足对页面的需要&#xff0c…

(学习日记)2023.4.10

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…