Java中代理模式有着广泛的应用,AOP就是最典型的应用。
Java中代理模式一般涉及到的角色有
1、抽象角色:一般是个接口,Java原生的代理模式也只支持接口代理
2、真实角色:实现抽象接口的真实类,又叫委托类。
3、代理角色:代理角色内部包含了真实角色的引用,且实现了与真实角色相同的接口,相当于对真实角色进行了封装。这样,代理角色可以执行真实角色的操作,还能额外附加自己的操作。
静态代理
我们通过代码,实现一个简单的静态代理。模拟一个场景,网易为暴雪代理魔兽世界。
抽象接口:魔兽世界——Wow
接口中一个待实现的方法:TBC(燃烧的远征)
实现类:暴雪——Blizzard
Java 代码1 | public class Blizzard implements Wow |
5 | System.out.println( "Let us start The Burning Crusade" ); |
实现类很简单,暴雪自己实现了tbc这个接口,开始燃烧的远征。
代理类:网易——NetEasy
Java 代码01 | public class NetEasy implements Wow |
03 | private Blizzard blizzard; |
05 | public NetEasy(Blizzard blizzard) |
07 | this .blizzard = blizzard; |
12 | System.out.println( "NetEasy proxy wow start" ); |
16 | System.out.println( "NetEasy proxy wow over" ); |
在代理类网易中,包含了暴雪的实例对象,网易在实现tbc接口时,加入了自己的逻辑,并执行了暴雪实现的方法。
写个工厂方法获取Wow的实例,对客户隐藏了实现,玩家不知道是返回的是暴雪还是网易。
-
Java 代码01 | public class StaticFactory |
03 | public static Wow getInstance() |
05 | Blizzard blizzard = new Blizzard(); |
06 | NetEasy neteasy = new NetEasy(blizzard); |
写个main函数测试下
Java 代码3 | public static void main(String[] args) |
5 | Wow wow = StaticFactory.getInstance(); |
这就是最简单的一个静态代理的模式,功能就是网易为暴雪代理魔兽世界。
静态代理的缺陷:
1、如果接口中方法很多,代理类对每个方法都做代理,那么静态代理类规模会非常庞大。
2、如果接口中增加/减少了一个方法,实现类和代理类都需要做相应的修改,牵一发动全身。
动态代理
Java动态代理,程序并不难写,但是想弄懂其中的原理,还是需要仔细研究jdk源码的。
还是以上面的例子说明,魔兽世界Wow这个接口,还有暴雪Blizzard这个实现类,都无须改动。我们增加一个动态代理处理器和工厂方法。
Java 代码01 | public class DynamicHandler implements InvocationHandler |
03 | private Object originalObj; |
05 | public DynamicHandler(Object originalObj) |
07 | this .originalObj = originalObj; |
10 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable |
12 | System.out.println( "dynamic proxy start" ); |
14 | Object result = method.invoke(originalObj, args); |
16 | System.out.println( "dynamic proxy over" ); |
DynamicHandler这个类很重要,它实现了InvocationHandler接口。Java中要实现动态代理,需要实现InvocationHandler这个接口。但是实现InvocationHandler接口的类,并不是动态代理类,动态代理类会在运行时生成,jvm在运行时,会根据实现了InvocationHandler接口的类生成一个动态代理类。因此有些文章将实现了InvocationHandler接口的类定义为动态代理类,这个是有欠妥当的,它其实只是一个handler,真正的动态代理类是运行时生成的。
我们看DynamicHandler这个类,它实现了InvocationHandler接口中的invoke方法,invoke方法中植入了自己的逻辑,并用反射的方式对委托类进行方法调用。仅仅看这个实现,其实很模糊,这个invoke(Object proxy, Method method, Object[] args)方法在哪里调用的?
我么再看下动态工厂,它会返回动态代理的实例。同样,对于客户而言,并不知道实现Wow接口的是委托类还是代理类。
Java 代码01 | public class DynamicFactory |
03 | public static Wow getInstance() |
05 | Blizzard blizzard = new Blizzard(); |
06 | InvocationHandler invocationHandler = new DynamicHandler(blizzard); |
08 | Wow proxy = (Wow)Proxy.newProxyInstance( |
09 | blizzard.getClass().getClassLoader(), |
10 | blizzard.getClass().getInterfaces(), |
看上面的代码,最关键的莫过于Proxy.newProxyInstance(blizzard.getClass().getClassLoader(), blizzard.getClass().getInterfaces(), invocatioonHandler)这句了。这一句在运行时会创建动态代理类并返回它的一个实例对象。我们看其源码实现。Proxy.newProxyInstance(...)源码较多,我们抽取其中的关键语句
Java 代码1 | Class<?> cl = getProxyClass0(loader, intfs); |
3 | final Constructor<?> cons = cl.getConstructor(constructorParams); |
5 | return cons.newInstance( new Object[]{h}); |
中间省略了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 代码1 | byte [] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); |
3 | return defineClass0(loader, proxyName, |
4 | proxyClassFile, 0 , proxyClassFile.length); |
ProxyGenerator.generateProxyClass()完成了生成字节码的动作,这个方法可以在运行时产生一个描述代理类字节码的byte[]数组。具体实现这里暂时不深究。
private static native Class<?> defineClass0(ClassLoader loader, String name,
byte[] b, int off, int len);
defineClass0则是一个native方法,根据classloader,字节码数组等动态生成一个class类。
我们写一个main方法,测试一下动态代理
Html 代码01 | public class DynamicMain |
03 | public static void main(String[] args) |
05 | System.getProperties().put( "sun.misc.ProxyGenerator.saveGeneratedFiles" , "true" ); |
07 | Wow wow = DynamicFactory.getInstance(); |
由于代理类是动态生成的,一般来说,并不能找到其class文件。我们加上System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");这句话,这样可以在磁盘中看到class文件,位于项目根目录下的com/sun/proxy路径下,文件名为$Proxy0.class。反编译一下,看其源码
Java 代码01 | public final class $Proxy0 extends Proxy implements Wow |
03 | private static Method m1; |
04 | private static Method m2; |
05 | private static Method m3; |
06 | private static Method m0; |
08 | public $Proxy0(InvocationHandler paramInvocationHandler) |
11 | super (paramInvocationHandler); |
16 | public final void tbc() |
21 | this .h.invoke( this , m3, null ); |
24 | catch (RuntimeException localRuntimeException) |
26 | throw localRuntimeException; |
28 | catch (Throwable localThrowable) |
30 | throw new UndeclaredThrowableException(localThrowable); |
38 | m1 = Class.forName( "java.lang.Object" ).getMethod( "equals" , new Class[] { Class.forName( "java.lang.Object" ) }); |
39 | m2 = Class.forName( "java.lang.Object" ).getMethod( "toString" , new Class[ 0 ]); |
40 | m3 = Class.forName( "com.huawei.proxy.statics.Wow" ).getMethod( "tbc" , new Class[ 0 ]); |
41 | m0 = Class.forName( "java.lang.Object" ).getMethod( "hashCode" , new Class[ 0 ]); |
44 | catch (NoSuchMethodException localNoSuchMethodException) |
46 | throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); |
48 | catch (ClassNotFoundException localClassNotFoundException) |
50 | throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); |
省略了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包实现。而且关于其中的动态字节码等关键技术,还有待深入研究。
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:hwclouds.bbs@huawei.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
评论(0)