• 基于JDK的动态代理原理分析


    基于JDK的动态代理原理分析

    这篇文章解决三个问题:

    1. What 动态代理是什么

    2. How 动态代理怎么用

    3. Why 动态代理的原理

    动态代理是什么?

    动态代理是代理模式的一种具体实现,是指在程序运行期间,动态的生成目标对象的代理类(直接加载在内存中的字节码文件),实现对目标对象所有方法的增强。通过这种方式,我们可以在不改变(或无法改变)目标对象源码的情况下,对目标对象的方法执行前后进行干预。

    动态代理怎么用?

    首先,准备好我们需要代理的类和接口,因为JDK的动态代理是基于接口实现的,所以被代理的对象必须要有接口

    /**
    * SaySomething接口
    */
    public interface SaySomething {

       public void sayHello();

       public void sayBye();
    }
    /**
    * SaySomething的实现类
    */
    public class SaySomethingImpl implements SaySomething {
       @Override
       public void sayHello() {
           System.out.println("Hello World");
      }

       @Override
       public void sayBye() {
           System.out.println("Bye Bye");
      }
    }

    按照动态代理的用法,需要自定义一个处理器,用来编写自定义逻辑,实现对被代理对象的增强。

    自定义的处理器需要满足以下要求:

    • 需要实现InvocationHandler,重写invoke方法,在invoke方法中通过加入自定义逻辑,实现对目标对象的增强。

    • 需要持有一个成员变量,成员变量的是被代理对象的实例,通过构造参数传入。(用来支持反射调用被代理对象的方法)

    • 需要提供一个参数为被代理对象接口类的有参构造。(用来支持反射调用被代理对象的方法)

    /**
    * 自定义的处理器,用来编写自定义逻辑,实现对被代理对象的增强
    */
    public class CustomHandler implements InvocationHandler {

       //需要有一个成员变量,成员变量为被代理对象,通过构造参数传入,用来支持方法的反射调用。
       private SaySomething obj;
       
       //需要有一个有参构造,通过构造函数将被代理对象的实例传入,用来支持方法的反射调用
       public CustomHandler(SaySomething obj) {
           this.obj = obj;
      }

       /**
        * proxy:动态生成的代理类对象com.sun.proxy.$Proxy0
        * method:被代理对象的真实的方法的Method对象
        * args:调用方法时的入参
        */
       @Override
       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           //目标方法执行前的自定义逻辑处理
           System.out.println("-----before------");

           //执行目标对象的方法,使用反射来执行方法,反射需要传入目标对象,此时用到了成员变量obj。
           Object result = method.invoke(obj, args);

           //目标方法执行后的自定义逻辑处理
           System.out.println("-----after------");
           return result;
      }
    }

    这样我们就完成了自定义处理器的编写,同时在invoke方法中实现对了代理对象方法的增强,被代理类的所有方法的执行都会执行我们自定义的逻辑。

    接下来,需要通过Proxy,newProxyInstance()方法来生成代理对象的实例,并进行方法调用测试。

    public class JdkProxyTest {
       public static void main(String[] args) {
           //将生成的代理对象的字节码文件 保存到硬盘
           System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

           //被代理对象的实例
           SaySomething obj = new SaySomethingImpl();
           //通过构造函数,传入被代理对象的实例,生成处理器的实例
           InvocationHandler handler = new CustomHandler(obj);
           //通过Proxy.newProxyInstance方法,传入被代理对象Class对象、处理器实例,生成代理对象实例
           SaySomething proxyInstance = (SaySomething) Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                                                                              new Class[]{SaySomething.class}, handler);
           //调用生成的代理对象的sayHello方法
           proxyInstance.sayHello();
           System.out.println("===================分割线==================");
           //调用生成的代理对象的sayBye方法
           proxyInstance.sayBye();
      }
    }

     

     

    运行main方法,查看控制台,大功告成。至此,我们已经完整的完成了一次动态代理的使用。

     

    动态代理的原理

    生成的proxyInstance对象到底是什么,为什么调用它的sayHello方法会执行CustomerHandler的invoke方法呢?

    直接贴上proxyInstance的字节码文件,我们就会恍然大悟了...

    //$Proxy0是SaySomething的实现类,重写了sayHello和sayBye方法
    public final class $Proxy0 extends Proxy implements SaySomething {
       private static Method m1;
       private static Method m3;
       private static Method m2;
       private static Method m4;
       private static Method m0;

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

       static {
           try {
               m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
               m3 = Class.forName("com.example.demo.hanmc.proxy.jdk.SaySomething").getMethod("sayHello");
               m2 = Class.forName("java.lang.Object").getMethod("toString");
               m4 = Class.forName("com.example.demo.hanmc.proxy.jdk.SaySomething").getMethod("sayBye");
               m0 = Class.forName("java.lang.Object").getMethod("hashCode");
          } catch (NoSuchMethodException var2) {
               throw new NoSuchMethodError(var2.getMessage());
          } catch (ClassNotFoundException var3) {
               throw new NoClassDefFoundError(var3.getMessage());
          }
      }
     
       //实现了接口的sayHello方法,在方法内部调用了CustomerHandler的invoke方法,同时传入了Method对象,
       //所以在CustomerHandler对象中可以通过mathod.invovke方法调用SyaSomthing的sayHello方法
       public final void sayHello() throws {
           try {
               //h是父类Proxy中的InvocationHandler对象,其实就是我们自定义的CustomHandler对象
               super.h.invoke(this, m3, (Object[])null);
          } catch (RuntimeException | Error var2) {
               throw var2;
          } catch (Throwable var3) {
               throw new UndeclaredThrowableException(var3);
          }
      }

       public final void sayBye() throws {
           try {
               super.h.invoke(this, m4, (Object[])null);
          } catch (RuntimeException | Error var2) {
               throw var2;
          } catch (Throwable var3) {
               throw new UndeclaredThrowableException(var3);
          }
      }
       public final int hashCode() throws {
          //忽略内容
      }
       public final boolean equals(Object var1) throws {
          //忽略内容
      }
       public final String toString() throws {
          //忽略内容
      }
    }

    看到了生成的代理对象的字节码文件,是不是一切都明白你了,原理竟然如此简单^_^

     

    本文为个人学习整理,如有描述错误或者对相关内容感兴趣,欢迎评论或私信交流,一起讨论、共同进步。

  • 相关阅读:
    Vue3生命周期
    Jetson Nano 部署(1):YOLOv5 目标检测实战介绍
    Python手动安装Jieba库(Win11)
    pulsar集群搭建_亲测成功
    阿里P7程序员斩获offer感言:突破Java瓶颈期我用了这一套路线图
    vue15
    交易所通用质押式回购
    面试算法2:二进制加法
    Rabbitmq的四种交换机类型
    如何使用透明贴图实现火焰效果
  • 原文地址:https://www.cnblogs.com/hanmingchao/p/16065772.html