基于JDK * 原理解析

作者:wen-pan 时间:2022-07-24 19:43:33 

1、回顾一下JDK * 的核心参数

如果我们要为target类创建一个【JDK * 对象】,那么我们必须要传入如下三个核心参数

  • 加载target类的类加载器

  • target类实现的接口

  • InvocationHandler

为什么必须要这三个参数呢?之前使用 * 的时候都是直接按接口要求传这三个参数,但从来没想过为什么?下面仔细去探究一下

2、实现一个简单的 *

【JDK * 】的核心其实是借助【Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)】方法,去创建的 * 对象,我们这里也使用这个方法去创建一个简单的【 * 对象】以便于理解他的核心原理。

①、定义接口Subject

public interface Subject {
   /**
    * 接口方法
    */
   void doSomething();
   /**
    * sayHello
    *
    * @param name name
    * @return string
    */
   String sayHello(String name);
}

②、定义接口的实现类RealSubject

实现Subject接口,并实现接口相关方法

public class RealSubject implements Subject {
   @Override
   public void doSomething() {
       System.out.println("RealSubject do something");
   }
   @Override
   public String sayHello(String name) {
       System.out.println("RealSubject sayHello");
       return "hello-" + name;
   }
}

③、定义代理对象创建工厂

  • 定义一个工厂类,该工厂类用于为target对象生产代理对象

  • 该工厂类借助Proxy.newProxyInstance来为目标对象创建代理对象

public class JdkDynamicProxyFactory {
   /**
    * 创建target类的代理对象
    * 注意:当调用代理对象中的方法时,其实就是调用的InvocationHandler里面的invoke方法,然后在invoke方法里调用目标对象对应的方法
    *
    * @param <T> 泛型
    * @return 代理对象
    */
   public static <T> T getProxy(Object target) {
       // 创建代理实例,分别传入:【加载target类的类加载器、target类实现的接口、InvocationHandler】
       Object proxyInstance = Proxy.newProxyInstance(
               target.getClass().getClassLoader(),
               target.getClass().getInterfaces(),
               new InvocationHandler() {
                   @Override
                   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                       System.out.println("执行目标方法前");
                       // 执行目标方法
                       Object result = method.invoke(target, args);
                       System.out.println("执行目标方法后");
                       // 返回目标方法的执行结果
                       return result;
                   }
               });
       // 返回代理对象
       return (T) proxyInstance;
   }
}

④、创建测试类,为target创建代理对象

该测试类会将内存中的 * 对象保存到磁盘上,以便于我们后续分析生成的 * 类的具体结构

public class Client {
   public static void main(String[] args) {
       // 保存生成的代理类的字节码文件
       System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
       // 目标对象
       RealSubject target = new RealSubject();
       // 使用JDK * 为【target对象】创建代理对象
       Subject proxy = JdkDynamicProxyFactory.getProxy(target);
       // 调用代理对象的方法
       proxy.doSomething();
       System.out.println("=====================华丽的分割线=====================");
       proxy.sayHello("wenpan");
   }
}

3、分析生成的 * 类的结构

在上面一步中我们使用

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

将动态生成的代理保存到了磁盘上,下面我们就具体看看生成的代理类长什么样

  • 可以看到代理类【继承了Proxy类】,并且【实现了目标接口Subject】,覆写了接口中的【每一个方法】

  • 这也说明了为什么JDK代理需要实现接口,因为Java是单继承的,既然代理类继承了Proxy类那么就无法再继承其他类了

  • 在代理类中将我们的【目标接口Subject】的【所有方法(包括object父类的方法)】都以【静态属性的形式】保存了起来(主要是为了方便后面的【反射调用】)

  • 在调用 * 对象的某个方法时(比如:调用doSomething方法),实质上是调用的【Proxy类】的【h属性】的invoke方法

  • 所以我们要重点去看看这个【Proxy.h】到底是个什么鬼?其实他就是创建代理对象是我们传入的【InvocationHandler】

// 1、代理类首先继承了Proxy类(这也说明了为什么JDK代理需要实现接口,因为Java是单继承的),并且实现了目标接口Subject
public final class $Proxy0 extends Proxy implements Subject {
 // 2、可以看到,在代理类中将我们的【目标接口Subject】的【所有方法(包括object父类的方法)】都以【静态属性的形式】保存了起来
   private static Method m1;
   private static Method m3;
   private static Method m4;
   private static Method m2;
   private static Method m0;
 // 以静态代码块的形式为属性赋值
   static {
         try {
             m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
             m3 = Class.forName("com.stone.design.mode.proxy.jdk.Subject").getMethod("doSomething");
             m4 = Class.forName("com.stone.design.mode.proxy.jdk.Subject").getMethod("sayHello", Class.forName("java.lang.String"));
             m2 = Class.forName("java.lang.Object").getMethod("toString");
             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);
   }
 // 3、object父类的equals方法
   public final boolean equals(Object var1) throws  {
       try {
         // 这里的supper是指的Proxy类,调用【Proxy类】的【h属性】的invoke方法执行
         // 重点:【注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看
         // 这里也就体现了创建代理对象时为什么需要传入【InvocationHandler】,以及为什么调用代理对象的方法时都是执行的invoke方法
           return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
       } catch (RuntimeException | Error var3) {
           throw var3;
       } catch (Throwable var4) {
           throw new UndeclaredThrowableException(var4);
       }
   }
 // 4、目标接口的方法
   public final void doSomething() throws  {
       try {
         // 调用了【Proxy.h属性】的invoke方法
         // 注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看
           super.h.invoke(this, m3, (Object[])null);
       } catch (RuntimeException | Error var2) {
           throw var2;
       } catch (Throwable var3) {
           throw new UndeclaredThrowableException(var3);
       }
   }
 // 5、目标接口的方法
   public final String sayHello(String var1) throws  {
       try {
         // 调用了【Proxy.h属性】的invoke方法
         // 注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看
           return (String)super.h.invoke(this, m4, new Object[]{var1});
       } catch (RuntimeException | Error var3) {
           throw var3;
       } catch (Throwable var4) {
           throw new UndeclaredThrowableException(var4);
       }
   }
   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);
       }
   }
}

4、Proxy.h是什么鬼

在上面的第三点中我们看到,执行代理对象的方法的时候其实质是调用的【super.h.invoke方法】也就是【Proxy.h.invoke方法】,那么我们仔细看看【Proxy.h】到底是什么鬼

①、Proxy.newProxyInstance是如何创建代理对象的

下面的源代码我做了一些删减,只留下了最核心的部分

  • 通过下面代码我们就明确了使用【newProxyInstance】方法创建代理对象时所做的几件事情

  • 首先通过【目标接口】 + 【类加载器】创建一个Proxy类的【Class对象】

  • 然后通过这个【Class对象】获取到他的有参构造器,并且传入我们自定义的【InvocationHandler】作为构造函数参数,并且通过反射的方式调用有参构造器创建一个【Proxy对象】

  • 在【Proxy的有参构造器】中,会将传入的【InvocationHandler】保存到 【h属性】上(方便后面的supper.h.invoke调用)

  • 代理对象创建完毕

public class Proxy implements java.io.Serializable {
 // h属性,保存我们传递进来的InvocationHandler
   protected InvocationHandler h;
   // 【有参构造器】注意这里的参数
   protected Proxy(InvocationHandler h) {
     Objects.requireNonNull(h);
     this.h = h;
   }
   // 生成代理对象的方法
   public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
       throws IllegalArgumentException{
     // 1、InvocationHandler强制不允许为空
       Objects.requireNonNull(h);
       // 获取到目标接口
       final Class<?>[] intfs = interfaces.clone();
       /*
        * Look up or generate the designated proxy class.
        * 2、获取到代理类的Class对象(也就是Proxy)
        */
       Class<?> cl = getProxyClass0(loader, intfs);
       /*
        * Invoke its constructor with the designated invocation handler.
        * 通过反射执行 cl 的有参构造,也就是下面这个,可以看到通过反射执行Proxy有参构造,
        * 将InvocationHandler赋值到了h属性上
        */
       try {
           // 3、获取到有参构造器
           final Constructor<?> cons = cl.getConstructor(constructorParams);
           // 4、通过构造器来创建一个代理对象并返回,这里传入的参数h 就是我们的【InvocationHandler】
           return cons.newInstance(new Object[]{h});
       } catch (IllegalAccessException|InstantiationException e) {
           // 省略....
       }
   }
}

5、问题总结

现在再来看上面抛出的三个问题!

为什么创建代理对象时需要传入如下三个参数:

  • 加载target类的类加载器

  • target类实现的接口

  • InvocationHandler

①、为什么要传入类加载器

因为 * 类也是类,我们普通的类是从【磁盘上的.class文件(也可以是其他地方,比如网络上)】里加载而来,而 * 类则是在【运行过程中动态生成的类】。

那么既然是类那么他就一定要【被类加载器加载后】才能被我们的【Java虚拟机】识别。

所以我们会传入【加载target类的类加载器】,用该类加载器来加载【动态生成的代理类】

②、为什么要传入target类实现的接口

为啥要传入【target类实现的接口】呢?直接【继承目标类】不行吗?肯定不行

  • 从上面的【动态生成的代理类的结构】来看,代理类继承了Proxy类,由于【Java是单继承】的,所以无法再通过继承的方式来继承【目标类】了。

  • 所以 * 类需要【实现目标接口】,来重写接口的方法

③、为什么要传入InvocationHandler

  • 从【动态生成的代理类的结构】可以看出,我们传入的InvocationHandler最终会被作为一个属性保存到Proxy对象的【h属性】上

  • 并且【 * 对象】会覆写【目标接口的所有方法】,在方法中会使用 supper.h.invoke的方式调用InvocationHandler的invoke方法,所以我们需要传入InvocationHandler

  • * 的每个方法调用都会先走InvocationHandler.invoke()方法

来源:https://blog.csdn.net/Hellowenpan/article/details/123482681

标签:JDK, , ,原理
0
投稿

猜你喜欢

  • MyBatis常用的jdbcType数据类型

    2023-09-18 19:09:35
  • 详谈java 堆区、方法区和栈区

    2023-11-23 18:35:22
  • JavaWeb Servlet实现文件上传与下载功能实例

    2023-06-16 16:41:27
  • Struts2下拉框实例解析

    2021-09-16 16:29:59
  • SpringBoot上传文件大小受限问题的解决办法

    2023-04-19 09:46:16
  • Java内存模型之happens-before概念详解

    2023-11-23 03:11:50
  • 避免sql注入_动力节点Java学院整理

    2023-08-21 17:39:12
  • 详解Java中的反射机制和动态代理

    2023-11-27 15:41:00
  • mybatis-plus 新增/修改如何实现自动填充指定字段

    2023-11-28 22:20:53
  • IDEA社区版下载安装流程详解(小白篇)

    2021-11-13 12:37:18
  • Java 多线程等待优雅的实现方式之Phaser同步屏障

    2023-11-29 09:47:43
  • 五分钟手撸一个Spring容器(萌芽版)

    2021-07-29 02:41:54
  • Java快速排序QuickSort(实例)

    2021-12-22 21:47:42
  • springmvc 参数绑定总结

    2023-11-16 21:30:44
  • Spring MVC请求参数的深入解析

    2021-11-26 22:55:25
  • SSM框架搭建图文教程(推荐)

    2023-11-10 20:39:07
  • Java享元设计模式优化对象创建提高性能和效率

    2022-05-26 23:12:02
  • Idea热加载插件JRebel激活以及使用教程

    2022-03-27 18:16:34
  • SpringMVC 向jsp页面传递数据库读取到的值方法

    2022-03-29 00:51:15
  • Java中LocalDateTime的具体用法

    2023-11-23 14:00:05
  • asp之家 软件编程 m.aspxhome.com