深入剖析java代理模式

网友投稿 483 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方法。

深入剖析java代理模式

这样一来,整个流程都串通了,我们终于弄清楚了整个流程。

总结

与静态代理相比,动态代理将接口中的所有方法都通过InvocationHandler的invoke方法处理,比较灵活。

静态代理的那些缺陷,动态代理基本都能解决。

从上面的实现中,我们还可以看到AOP的一个雏形。

Java中自带的动态代理也有一定的局限性,它只是针对接口而言的,如果想对class对代理,则必须借助CGLib等开源jar包实现。而且关于其中的动态字节码等关键技术,还有待深入研究。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:提高软件开发性能的方案
下一篇:系统调用有哪些类别?如何进行?有什么作用?
相关文章