从生成的代理类看动态代理机制 男娘i 2024-03-23 19:47 48阅读 0赞 ## 引言 ## 上一篇文章我们分析了InvocationHandler的注释,并从注释中总结出了一些问题。到现在为止,我们只是知道使用动态代理可能出现哪些问题,对于动态代理的底层实现机制并不清楚,看完这篇文章,相信你会对动态代理有个更加深刻的理解,我们之前发现的那些问题和疑惑,也能得到正确的解答。 ## 最终结果 ## 按照逻辑,这个时候我们应该分析Proxy类的源码,但是源码分析总是枯燥乏味的,如果没有一个可以直接展示动态代理机制的生成物,我们肯定要去分析源码。幸运的是,动态代理中,运行时为我们偷偷生成了一个动态代理类,通过分析这个类的特性,我们就能轻松掌握动态代理的精髓。 我在这里先把这个动态代理类展示出来。 ### 生成的代理类 ### 在main方法中加入这行代码: `System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");` 就能在当前工程的com.sun.proxy目录下生成一个名为$Proxy0.class文件,这是我的工程目录: ![watermark_image_bG9nby9jc2RuXzEucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLGhfNDA_g_se_x_0_y_0_t_100][] 直接使用IDEA打开这个文件或者使用JD-GUI进行反编译,可以看到类的内容如下(为了之后的详细描述,我把整个类的代码都展示了出来): \`\`\` public final class $Proxy0 extends Proxy implements IHello \{ private static Method m1; private static Method m3; private static Method m5; private static Method m4; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { 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 void sayHello() 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 returnAString() throws { try { return (String)super.h.invoke(this, m5, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int returnAInt() throws { try { return (Integer)super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } 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); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("person.andy.concurrency.proxy.IHello").getMethod("sayHello"); m5 = Class.forName("person.andy.concurrency.proxy.IHello").getMethod("returnAString"); m4 = Class.forName("person.andy.concurrency.proxy.IHello").getMethod("returnAInt"); 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()); } } \} \`\`\` 这个类就是之前注释里出现了很多次的代理类。当我们使用动态代理,或者更具体的说,调用Proxy.newProxyInstance方法的时候,虚拟机就会自动为我们生成一个这样的类,接下来我们就分析一下这个类。 ### 代理类的特性 ### 我们看到这个代理类的第一印象,应该就是继承了Proxy类和实现了IHello接口。 那我们瞬间就明白了,为什么jdk的动态代理要求被增强的类必须实现某个接口,因为代理类本身已经继承了Proxy类,要想对类方法进行增强,只能去实现被增强的类实现的接口,也就是这里的IHello。 再看构造方法,是一个参数为InvocationHandler的构造方法,这个方法也是父类Proxy的构造方法,通过这个构造方法,就把动态代理中两个最重要的类,Proxy和InvocationHandler联系起来了。 既然实现了IHello接口,那么必然就要对接口方法进行实现,那么代理类是怎么实现的呢?我们看到这个类中声明了几个Method方法并在静态块中进行了初始化,而这几个方法里面就有IHello中需要实现的几个方法。除了接口中的方法,还有Object的几个方法。 `static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("person.andy.concurrency.proxy.IHello").getMethod("sayHello"); m5 = Class.forName("person.andy.concurrency.proxy.IHello").getMethod("returnAString"); m4 = Class.forName("person.andy.concurrency.proxy.IHello").getMethod("returnAInt"); 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()); } }` 那这几个方法是怎么实现的呢? 还记得InvocationHandler注释中写的吗,proxy实例的方法调用都会被转发到与它相关的InvocationHandler上面来,由调用invoke方法来实现。我们拿带有返回值的returnAString()方法来举例: `public final String returnAString() throws { try { return (String)super.h.invoke(this, m5, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }` 它果然调用了自己的InvocationHandler的invoke方法,并且第一个参数代理实例传入的是this,第二个方法实例传入的就是在静态块里初始化的IHello的returnAString方法,由于方法执行没有参数,所以第三个参数为null; 看到这里,我们就能理解之前类型兼容的问题: invoke方法和接口中声明的方法的返回值类型兼容问题。因为代理类本质上实现了接口,需要实现接口方法,而实现接口方法的方式就是调用InvocationHandler的invoke方法,所以必然需要invoke方法的返回值类型与接口方法的返回值类型一致,并且由于代理类的每个需要实现的接口方法(在这里是sayHello和returnAString)都需要调用invoke方法,所以一般的写法就是 `return method.invoke(originalObj, args);` 来让这两个返回值保持一致,所以invoke方法适合对方法做增强之后将原本的方法调用返回值返回。 那我们应该就能描述出动态代理是怎么实现的了: (1)我们想要对某个类(Hello)的方法进行增强,这个类已经实现了某个接口(IHello)。 (2)为了实现增强的目的,我们定义了InvocationHandler接口,这个接口的invoke方法可以完成需要增强的逻辑,这里就是: `System.out.println("welcome");` 增强之后还能接着执行被增强的类的方法,也就是 `method.invoke(originalObj, args);` 我们唯一需要做的就是将被增强的类与InvocationHandler绑定起来。所以说,我们的InvocationHandler决定了对哪个类增强以及如何增强。 (3)解决了怎么增强方法的问题,就需要解决怎么进行方法调用转发的问题。解决的方法就是创建代理类$Proxy0。我们让这个类实现被增强的类(Hello)的所有接口,这样就需要实现所有的接口方法了,然后代理类中有InvocationHandler作为成员变量,所有的接口方法实现都调用这个InvocationHandler的invoke方法,这样就完成了方法调用的转发。 (4)在整个实现中,InvocationHandler决定增强哪个类以及怎样增强这个类,proxy负责方法调用的转发,动态 代理的动态性就在这里,如果我想代理其他的类,我们只需要在bind方法中传入需要代理的类就行了,Proxy.newProxyInstance会自动为我们生成正确的代理类。如果你不能理解的话,请看下面的例子: 我新创建了一个ISell接口和一个实现类Sell: `public interface ISell { void sell(); }` `public class Sell implements ISell { @Override public void sell() { System.out.println("i am a seller"); } }` 怎么对这个Sell进行代理呢,很简单: `public static void main(String[] args) { System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); IHello iHello = (IHello) new DynamicProxy().bind(new Hello()); iHello.sayHello(); ISell iSell = (ISell) new DynamicProxy().bind(new Sell()); iSell.sell(); }` 你不需要修改DynamicProxy的实现,只需要新创建DynamicProxy,然后绑定我们的Sell就可以了。 ## 几个细节 ## ### invoke方法中使用动态代理实例的问题 ### 因为invoke的第一个参数我们传入的是this,也就是当前代理类的实例,所以如果我们将invoke方法从: `@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("welcome"); return method.invoke(originalObj, args); }` 改成 `@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("welcome"); return method.invoke(proxy, args); }` 注意变化,method.invoke的第一个参数从我们增强的Hello对象变成了代理类对象,就会发生很严重的问题,以sayHello方法来说,代理对象的sayHello方法是通过调用invoke方法实现的,而invoke方法由调用了代理对象的sayHello方法,这样就会无限循环调用下去,最后的结果就是报错。 ### toString方法的问题 ### 我们看到,在代理类里面,不仅接口的方法通过invoke方法来实现,object类的几个方法,toString、equals和hashCode方法也是通过invoke方法来实现的,我们重点看toString方法: `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); } } m2 = Class.forName("java.lang.Object").getMethod("toString");` 这个m2是Object类的方法,也就是说传给invoke方法的method参数是Object的toString方法。 我们在invoke方法中增加一条输出,输出第一个参数: `@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(proxy); System.out.println("welcome"); return method.invoke(originalObj, args); }` 想一下过程,我们使用System.out.println方法输出某个对象时,会调用对象的toString方法,而proxy的toString方法就是上面代理类中的调用invoke的那个toString方法,也就是会调用invoke方法,然后又输出proxy,继续调用toString方法,一直循环调用下去,直到栈溢出。 运行的结果也是这样: `Exception in thread "main" java.lang.StackOverflowError at person.andy.concurrency.proxy.DynamicProxy.invoke(DynamicProxy.java:16) at com.sun.proxy.$Proxy0.toString(Unknown Source) at java.lang.String.valueOf(String.java:2994) at java.io.PrintStream.println(PrintStream.java:821) at person.andy.concurrency.proxy.DynamicProxy.invoke(DynamicProxy.java:16) at com.sun.proxy.$Proxy0.toString(Unknown Source) at java.lang.String.valueOf(String.java:2994) at java.io.PrintStream.println(PrintStream.java:821) at person.andy.concurrency.proxy.DynamicProxy.invoke(DynamicProxy.java:16) at com.sun.proxy.$Proxy0.toString(Unknown Source)` 这可以看成是jdk动态代理的一个小问题。 ## 小结 ## 这篇文章没有分析Proxy的源码,而是直接通过动态生成的代理类讲解了使用动态代理的时候运行时为我们做了哪些工作。相信读到这里,动态代理的实现机制已经相当明了了。 如果不去深挖动态代理类到底是怎么创建的,jdk动态代理这个主题完全可以到此为止。不过,了解运行时怎么为我们生成这个动态代理类,对于理解字节码生成技术很有帮助。从下一篇文章开始,我们就开始介绍动态代理技术中代理类的创建过程。 [watermark_image_bG9nby9jc2RuXzEucG5nP3gtb3NzLXByb2Nlc3M9aW1hZ2UvcmVzaXplLGhfNDA_g_se_x_0_y_0_t_100]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/14/15916bb90e714c27a52fbf0b26b06960.png
相关 Java动态代理常见问题:代理类生成、方法调用等 在Java中使用动态代理,可能会遇到以下一些具体问题: 1. 代理类的创建:动态代理需要通过Class对象创建代理类。这个过程通常由JDK的Proxy类自动完成。 2. 方 ╰+哭是因爲堅強的太久メ/ 2024年09月27日 06:00/ 0 赞/ 54 阅读
相关 Java动态代理:代理类的创建过程 Java动态代理是Java语言提供的一种动态行为创建技术。它不需要在编译时就知道要创建什么样的代理,而是运行时根据需要动态决定。 代理类的创建过程主要包括以下几个步骤: 1 分手后的思念是犯贱/ 2024年09月10日 02:45/ 0 赞/ 53 阅读
相关 从生成的代理类看动态代理机制 引言 上一篇文章我们分析了InvocationHandler的注释,并从注释中总结出了一些问题。到现在为止,我们只是知道使用动态代理可能出现哪些问题,对于动态代理的底层实 男娘i/ 2024年03月23日 19:47/ 0 赞/ 49 阅读
相关 从java 反射看延迟加载(代理模式)(六)——动态代理与静态代理 说实在的,代理模式这个模式我现在闭着眼睛都能写出来,真的是快要看吐了。 一、先看看代理模式的uml图 ![Center][] 图讲的很清楚了,我 冷不防/ 2022年07月14日 05:08/ 0 赞/ 104 阅读
相关 动态代理机制底层 动态代理机制作用和应用场景: 1. 日志集中打印; 2. 事务; 3. 权限管理 4. AOP SpringAOP中可以用那些方式实现,区别? 1. c 朱雀/ 2022年06月01日 09:36/ 0 赞/ 256 阅读
相关 设计模式---代理模式,从实例看静态代理,动态代理,CGLIB 前言 最近完成了自己的个人博客项目,要继续学习Spring了,AOP用的是动态代理,今天特地好好理解一下代理模式 路线 静态代理 jdk动态代理 约定不等于承诺〃/ 2022年05月10日 07:00/ 0 赞/ 255 阅读
相关 Java动态代理机制 一、引言 曾经的一篇文章,[使用代理(Proxy)实现Spring的AOP功能][Proxy_Spring_AOP],就是使用Java的动态代理机制实现的。 对于动态代 叁歲伎倆/ 2022年03月16日 03:22/ 0 赞/ 263 阅读
相关 jdk动态代理机制 原文链接: https://www.cnblogs.com/xiaoluo501395377/p/3383130.html 在学习Spring的时候,我们知道Spring主要 男娘i/ 2021年12月22日 03:05/ 0 赞/ 287 阅读
还没有评论,来说两句吧...