深入剖析java代理模式

举报
容器小A 发表于 2017/09/29 16:17:30 2017/09/29
【摘要】 Java中代理模式有着广泛的应用,AOP就是最典型的应用。 Java中代理模式一般涉及到的角色有 1、抽象角色:一般是个接口,Java原生的代理模式也只支持接口代理 2、真实角色:实现抽象接口的真实类,又叫委托类。 3、代理角色:代理角色内部包含了真实角色的引用,且实现了与真实角色相同的接口,相当于对真实角色进行了封装。这样,代理角色可以执行真实角色的操作,还能额外附加自己的操作。

Java中代理模式有着广泛的应用,AOP就是最典型的应用。

Java中代理模式一般涉及到的角色有

1、抽象角色:一般是个接口,Java原生的代理模式也只支持接口代理

2、真实角色:实现抽象接口的真实类,又叫委托类。

3、代理角色:代理角色内部包含了真实角色的引用,且实现了与真实角色相同的接口,相当于对真实角色进行了封装。这样,代理角色可以执行真实角色的操作,还能额外附加自己的操作。

静态代理

我们通过代码,实现一个简单的静态代理。模拟一个场景,网易为暴雪代理魔兽世界。

抽象接口:魔兽世界——Wow

-
Java 代码
1public interface Wow
2{
3    void tbc();
4}

接口中一个待实现的方法:TBC(燃烧的远征)

实现类:暴雪——Blizzard

Java 代码
1public class Blizzard implements Wow
2{
3    public void tbc()
4    {
5        System.out.println("Let us start The Burning Crusade");
6    }
7}

实现类很简单,暴雪自己实现了tbc这个接口,开始燃烧的远征。

代理类:网易——NetEasy

Java 代码
01public class NetEasy implements Wow
02{
03    private Blizzard blizzard;
04 
05    public NetEasy(Blizzard blizzard)
06    {
07        this.blizzard = blizzard;
08    }
09 
10    public void tbc()
11    {
12        System.out.println("NetEasy proxy wow start");
13 
14        blizzard.tbc();
15 
16        System.out.println("NetEasy proxy wow over");
17    }
18}

在代理类网易中,包含了暴雪的实例对象,网易在实现tbc接口时,加入了自己的逻辑,并执行了暴雪实现的方法。

写个工厂方法获取Wow的实例,对客户隐藏了实现,玩家不知道是返回的是暴雪还是网易。

-
Java 代码
01public class StaticFactory
02{
03    public static Wow getInstance()
04    {
05        Blizzard blizzard = new Blizzard();
06        NetEasy neteasy = new NetEasy(blizzard);
07 
08        return neteasy;
09    }
10}

写个main函数测试下

Java 代码
1public class StaticMain
2{
3    public static void main(String[] args)
4    {
5        Wow wow = StaticFactory.getInstance();
6 
7        wow.tbc();
8    }
9}

这就是最简单的一个静态代理的模式,功能就是网易为暴雪代理魔兽世界。

静态代理的缺陷:

1、如果接口中方法很多,代理类对每个方法都做代理,那么静态代理类规模会非常庞大。

2、如果接口中增加/减少了一个方法,实现类和代理类都需要做相应的修改,牵一发动全身。

动态代理

Java动态代理,程序并不难写,但是想弄懂其中的原理,还是需要仔细研究jdk源码的。

还是以上面的例子说明,魔兽世界Wow这个接口,还有暴雪Blizzard这个实现类,都无须改动。我们增加一个动态代理处理器和工厂方法。

Java 代码
01public class DynamicHandler implements InvocationHandler
02{
03    private Object originalObj;
04 
05    public DynamicHandler(Object originalObj)
06    {
07        this.originalObj = originalObj;
08    }
09 
10    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
11    {
12        System.out.println("dynamic proxy start");
13 
14        Object result = method.invoke(originalObj, args);
15 
16        System.out.println("dynamic proxy over");
17 
18        return result;
19    }
20}

DynamicHandler这个类很重要,它实现了InvocationHandler接口。Java中要实现动态代理,需要实现InvocationHandler这个接口。但是实现InvocationHandler接口的类,并不是动态代理类,动态代理类会在运行时生成,jvm在运行时,会根据实现了InvocationHandler接口的类生成一个动态代理类。因此有些文章将实现了InvocationHandler接口的类定义为动态代理类,这个是有欠妥当的,它其实只是一个handler,真正的动态代理类是运行时生成的。

我们看DynamicHandler这个类,它实现了InvocationHandler接口中的invoke方法,invoke方法中植入了自己的逻辑,并用反射的方式对委托类进行方法调用。仅仅看这个实现,其实很模糊,这个invoke(Object proxy, Method method, Object[] args)方法在哪里调用的?

我么再看下动态工厂,它会返回动态代理的实例。同样,对于客户而言,并不知道实现Wow接口的是委托类还是代理类。

Java 代码
01public class DynamicFactory
02{
03    public static Wow getInstance()
04    {
05        Blizzard blizzard = new Blizzard();
06        InvocationHandler invocationHandler = new DynamicHandler(blizzard);
07 
08        Wow proxy = (Wow)Proxy.newProxyInstance(
09                blizzard.getClass().getClassLoader(),
10                blizzard.getClass().getInterfaces(),
11                invocationHandler);
12 
13        return proxy;
14    }
15}

看上面的代码,最关键的莫过于Proxy.newProxyInstance(blizzard.getClass().getClassLoader(), blizzard.getClass().getInterfaces(), invocatioonHandler)这句了。这一句在运行时会创建动态代理类并返回它的一个实例对象。我们看其源码实现。Proxy.newProxyInstance(...)源码较多,我们抽取其中的关键语句

Java 代码
1Class<?> cl = getProxyClass0(loader, intfs);
2  
3final Constructor<?> cons = cl.getConstructor(constructorParams);
4 
5return 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 代码
1byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
2 
3return defineClass0(loader, proxyName,
4proxyClassFile, 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 代码
01public class DynamicMain
02{
03    public static void main(String[] args)
04    {
05        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
06 
07        Wow wow = DynamicFactory.getInstance();
08 
09        wow.tbc();
10    }
11}

由于代理类是动态生成的,一般来说,并不能找到其class文件。我们加上System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");这句话,这样可以在磁盘中看到class文件,位于项目根目录下的com/sun/proxy路径下,文件名为$Proxy0.class。反编译一下,看其源码

Java 代码
01public final class $Proxy0 extends Proxy implements Wow
02{
03  private static Method m1;
04  private static Method m2;
05  private static Method m3;
06  private static Method m0;
07 
08  public $Proxy0(InvocationHandler paramInvocationHandler)
09    throws
10  {
11    super(paramInvocationHandler);
12  }
13   
14  // 省略equals, hashcode, toString方法 
15   
16  public final void tbc()
17    throws
18  {
19    try
20    {
21      this.h.invoke(this, m3, null);
22      return;
23    }
24    catch (RuntimeException localRuntimeException)
25    {
26      throw localRuntimeException;
27    }
28    catch (Throwable localThrowable)
29    {
30      throw new UndeclaredThrowableException(localThrowable);
31    }
32  }
33 
34  static
35  {
36    try
37    {
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]);
42      return;
43    }
44    catch (NoSuchMethodException localNoSuchMethodException)
45    {
46      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
47    }
48    catch (ClassNotFoundException localClassNotFoundException)
49    {
50      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
51    }
52  }
53}

省略了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进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。