欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JDK动态代理原理解析

程序员文章站 2022-03-10 10:29:37
JDK动态代理原理解析本文章不介绍什么是动态代理,而是讲解原理Demo代码我有一个Animal接口public interface Animal { void eat();}有一个Dog类实现了Animal接口public class Dog implements Animal { @Override public void eat() { System.out.println("animal eat..."); }}现在我想调用eat方...

JDK动态代理原理解析

本文章不介绍什么是动态代理,而是讲解原理

Demo代码

我有一个Animal接口

public interface Animal { void eat(); } 

有一个Dog类实现了Animal接口

public class Dog implements Animal { @Override public void eat() { System.out.println("animal eat..."); } } 

现在我想调用eat方法之前打印日志"before",之后打印"after", 我的代码如下

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /**
 * @author hh
 * @date 2020/9/29
 */ public class JdkProxyDemo implements InvocationHandler { static Animal animal = new Dog(); public static void main(String[] args) { Animal proxyAnimal = (Animal) Proxy.newProxyInstance( JdkProxyDemo.class.getClassLoader(), new Class[]{Animal.class}, new JdkProxyDemo()); System.out.println(proxyAnimal.getClass().getName()); proxyAnimal.eat(); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before..."); Object invoke = method.invoke(animal, args); System.out.println("after ..."); return invoke; } } 

执行上面main方法,成功的达到了我想要的结果

com.sun.proxy.$Proxy0
before... animal eat... after ... 

解密Animal animal = (Animal) Proxy.newProxyInstance(xxx)

这个animal实例就是一个jdk使用sun.misc.ProxyGenerator生成的代理对象(sun.misc.ProxyGenerator#generateProxyClass),ProxyGenerator的原理就是一个懂class文件规范的人,在代码里直接生产class文件字节码,然后使用ClassLoader加载到内存,有兴趣可以自己研究。

代理类animal的class文件如下:

 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 Animal { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) { super(var1); } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("hh.proxy.jdk.Animal").getMethod("eat"); 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 final boolean equals(Object var1) { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() { 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() { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void eat() { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } } 

我们调用eat方法,实际上调用了代理类的eat方法:

 public final void eat() { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } 

h就是InvocationHandler,也就是我上面的main方法所在的类, 他实现了这个接口
this就是proxy类,所以在:

 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before..."); Object invoke = method.invoke(animal, args); System.out.println("after ..."); return invoke; } 

方法中不能使用proxy, 他是代理类, 如果使用了他就是代理类自己玩自己, 一直玩下去,递归调用

至于为什么super.h.invoke(this, m3, (Object[])null); 方法是m3=eat方法, 因为这个m3也是动态代理作者起的名字,他当然知道m3是哪个方法了.

可以debug吗

不可以, 因为就算你把$proxy0反编译放到idea中, 他的字节码行号和源码行号对应不上,debug就算生效,行号也是对不上的, 但是,你可以debug的时候切换到proxy类, 看他的static字段,也就是m1,m2.m3…, 你可以看到m3确实是eat方法,比如:
JDK动态代理原理解析

怎么生成proxy源代码

使用ProxyGenerator, 或者使用hsdb工具, 这个可以百度搜索一大堆, 这里就不介绍了

与cglib有什么区别

  • 1、jdk代理需要有一个接口
  • 2、jdk代理最后走的还是method.invoke(…)使用的是反射,效率比较低(可以是试试class.newInstace()和new Object()的效率就知道了)

本文地址:https://blog.csdn.net/qq_31543867/article/details/108875947

相关标签: 代理 proxy