深入浅析Excel循环引用(excel循环引用如何解决)
545
2022-05-29
Java中代理模式有着广泛的应用,AOP就是最典型的应用。
Java中代理模式一般涉及到的角色有
1、抽象角色:一般是个接口,Java原生的代理模式也只支持接口代理
2、真实角色:实现抽象接口的真实类,又叫委托类。
3、代理角色:代理角色内部包含了真实角色的引用,且实现了与真实角色相同的接口,相当于对真实角色进行了封装。这样,代理角色可以执行真实角色的操作,还能额外附加自己的操作。
静态代理
我们通过代码,实现一个简单的静态代理。模拟一个场景,网易为暴雪代理魔兽世界。
抽象接口:魔兽世界——Wow
-
Java 代码
-
查看代码打印?
接口中一个待实现的方法:TBC(燃烧的远征)
实现类:暴雪——Blizzard
Java 代码
查看代码打印?
实现类很简单,暴雪自己实现了tbc这个接口,开始燃烧的远征。
代理类:网易——NetEasy
Java 代码
查看代码打印?
在代理类网易中,包含了暴雪的实例对象,网易在实现tbc接口时,加入了自己的逻辑,并执行了暴雪实现的方法。
写个工厂方法获取Wow的实例,对客户隐藏了实现,玩家不知道是返回的是暴雪还是网易。
-
Java 代码
-
查看代码打印?
写个main函数测试下
Java 代码
查看代码打印?
这就是最简单的一个静态代理的模式,功能就是网易为暴雪代理魔兽世界。
静态代理的缺陷:
1、如果接口中方法很多,代理类对每个方法都做代理,那么静态代理类规模会非常庞大。
2、如果接口中增加/减少了一个方法,实现类和代理类都需要做相应的修改,牵一发动全身。
动态代理
Java动态代理,程序并不难写,但是想弄懂其中的原理,还是需要仔细研究jdk源码的。
还是以上面的例子说明,魔兽世界Wow这个接口,还有暴雪Blizzard这个实现类,都无须改动。我们增加一个动态代理处理器和工厂方法。
Java 代码
查看代码打印?
DynamicHandler这个类很重要,它实现了InvocationHandler接口。Java中要实现动态代理,需要实现InvocationHandler这个接口。但是实现InvocationHandler接口的类,并不是动态代理类,动态代理类会在运行时生成,jvm在运行时,会根据实现了InvocationHandler接口的类生成一个动态代理类。因此有些文章将实现了InvocationHandler接口的类定义为动态代理类,这个是有欠妥当的,它其实只是一个handler,真正的动态代理类是运行时生成的。
我们看DynamicHandler这个类,它实现了InvocationHandler接口中的invoke方法,invoke方法中植入了自己的逻辑,并用反射的方式对委托类进行方法调用。仅仅看这个实现,其实很模糊,这个invoke(Object proxy, Method method, Object[] args)方法在哪里调用的?
我么再看下动态工厂,它会返回动态代理的实例。同样,对于客户而言,并不知道实现Wow接口的是委托类还是代理类。
Java 代码
查看代码打印?
看上面的代码,最关键的莫过于Proxy.newProxyInstance(blizzard.getClass().getClassLoader(), blizzard.getClass().getInterfaces(), invocatioonHandler)这句了。这一句在运行时会创建动态代理类并返回它的一个实例对象。我们看其源码实现。Proxy.newProxyInstance(...)源码较多,我们抽取其中的关键语句
Java 代码
查看代码打印?
中间省略了N句,
(1)Class> cl = getProxyClass0(loader, intfs)返回运行时生成的动态代理类。
(2)final Constructor> cons = cl.getConstructor(constructorParams)返回该代理类的构造函数。
(3)cons.newInstance(new Object[]{h})则通过构造函数创建代理类的实例对象,注意参数h就是传递的实现InvocationHandler接口的对象实例。
其中最关键的还是Class> cl = getProxyClass0(loader, intfs); 这句生成了动态代理类。跟踪源代码,最终找到以下语句
Java 代码
查看代码打印?
ProxyGenerator.generateProxyClass()完成了生成字节码的动作,这个方法可以在运行时产生一个描述代理类字节码的byte[]数组。具体实现这里暂时不深究。
private static native Class> defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);
defineClass0则是一个native方法,根据classloader,字节码数组等动态生成一个class类。
我们写一个main方法,测试一下动态代理
Html 代码
查看代码打印?
由于代理类是动态生成的,一般来说,并不能找到其class文件。我们加上System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");这句话,这样可以在磁盘中看到class文件,位于项目根目录下的com/sun/proxy路径下,文件名为$Proxy0.class。反编译一下,看其源码
Java 代码
查看代码打印?
省略了equals, hashcode, toString方法,和tbc()的实现是类似的。
可以看到$Proxy0这个类继承了Proxy类并实现了Wow接口。
由于继承了Proxy类,因此拥有了InvocationHandler的实现类的实例,上面在DynamicFactory中,我们是通过Proxy.newInstance(......)传递了InvocationHandler的实现类实例的。
$Proxy0中tbc()的实现,this.h.invoke(this, m3, null); 其实就是调用了DynamicHandler中的invoke方法。
这样一来,整个流程都串通了,我们终于弄清楚了整个流程。
总结
与静态代理相比,动态代理将接口中的所有方法都通过InvocationHandler的invoke方法处理,比较灵活。
静态代理的那些缺陷,动态代理基本都能解决。
从上面的实现中,我们还可以看到AOP的一个雏形。
Java中自带的动态代理也有一定的局限性,它只是针对接口而言的,如果想对class对代理,则必须借助CGLib等开源jar包实现。而且关于其中的动态字节码等关键技术,还有待深入研究。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。