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

2022年5月31日10:28:42

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

这篇文章解决三个问题:

  1. What 动态代理是什么

  2. How 动态代理怎么用

  3. Why 动态代理的原理

动态代理是什么?

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

动态代理怎么用?

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

/**
* SaySomething接口
*/
publicinterfaceSaySomething {

publicvoidsayHello();

publicvoidsayBye();
}
/**
* SaySomething的实现类
*/
publicclassSaySomethingImplimplementsSaySomething {
@Override
publicvoidsayHello() {
System.out.println("Hello World");
  }

@Override
publicvoidsayBye() {
System.out.println("Bye Bye");
  }
}

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

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

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

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

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

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

//需要有一个成员变量,成员变量为被代理对象,通过构造参数传入,用来支持方法的反射调用。
privateSaySomethingobj;

//需要有一个有参构造,通过构造函数将被代理对象的实例传入,用来支持方法的反射调用
publicCustomHandler(SaySomethingobj) {
this.obj=obj;
  }

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

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

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

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

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

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

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

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

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

动态代理的原理

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

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

//$Proxy0是SaySomething的实现类,重写了sayHello和sayBye方法
publicfinalclass$Proxy0extendsProxyimplementsSaySomething {
privatestaticMethodm1;
privatestaticMethodm3;
privatestaticMethodm2;
privatestaticMethodm4;
privatestaticMethodm0;

public$Proxy0(InvocationHandlervar1)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 (NoSuchMethodExceptionvar2) {
thrownewNoSuchMethodError(var2.getMessage());
      }catch (ClassNotFoundExceptionvar3) {
thrownewNoClassDefFoundError(var3.getMessage());
      }
  }

//实现了接口的sayHello方法,在方法内部调用了CustomerHandler的invoke方法,同时传入了Method对象,
//所以在CustomerHandler对象中可以通过mathod.invovke方法调用SyaSomthing的sayHello方法
publicfinalvoidsayHello()throws {
try {
//h是父类Proxy中的InvocationHandler对象,其实就是我们自定义的CustomHandler对象
super.h.invoke(this,m3, (Object[])null);
      }catch (RuntimeException|Errorvar2) {
throwvar2;
      }catch (Throwablevar3) {
thrownewUndeclaredThrowableException(var3);
      }
  }

publicfinalvoidsayBye()throws {
try {
super.h.invoke(this,m4, (Object[])null);
      }catch (RuntimeException|Errorvar2) {
throwvar2;
      }catch (Throwablevar3) {
thrownewUndeclaredThrowableException(var3);
      }
  }
publicfinalinthashCode()throws {
//忽略内容
  }
publicfinalbooleanequals(Objectvar1)throws {
//忽略内容
  }
publicfinalStringtoString()throws {
//忽略内容
  }
}

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

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

  • 作者:韩·某某
  • 原文链接:https://www.cnblogs.com/hanmingchao/p/16065772.html
    更新时间:2022年5月31日10:28:42 ,共 3937 字。