Java代理模式

代理模式(Proxy Pattern)的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

举个例子:

比如国内网络无法直接访问google等外国网址,但是通过访问可以登陆google的服务器就可以实现跨国访问,具体访问如下:
1).本机将网络请求发送给代理服务器
2).代理服务器转发请求给web服务器
3).web服务器返回结果给代理服务器
4).代理服务器转发返回结果给本机
从上面的流程来看,处于转发的服务器实际上就是一个代理,全权负责用户的上网行为转发到实际的委托服务器上。

Java中,代理模式的实现方式有2种:

  • 静态代理:代理类的具体实现在编译时就已经确定了,编译完成后是一个具体的.class文件
  • 动态代理:代理类是在JVM运行期间才动态生成的。

静态代理实现:

由于代理类全权处理被委托类的方法,所以一般的写法是通过公共接口规范代理类和被委托类的实现。

  public interface ICrossWall {
        void visitGoogle();

        void visitYoutube();
    }

被委托类

  //被委托类:web服务器
    public class WebServer implements ICrossWall {
        @Override
        public void visitGoogle() {
            System.out.println("real subject:send http request to visit google");
        }

        @Override
        public void visitYoutube() {
            System.out.println("real subject:send http request to visit youtube");
        }
    }

代理类

  //代理类:处于中间的转发服务器
    public class ProxyServer implements ICrossWall {

        //持有具体被委托类实例
        private ICrossWall subject;

        public ProxyServer(ICrossWall subject) {
            this.subject = subject;
        }

        @Override
        public void visitGoogle() {
            System.out.println("proxy:forward http request:google");
            subject.visitGoogle();
        }

        @Override
        public void visitYoutube() {
            System.out.println("proxy:forward http request:youtube");
            subject.visitYoutube();
        }
    }

本机访问

  public static void main(String[] args) {

        //创建一个被委托类
        ICrossWall webServer = new WebServer();
        //创建一个代理类
        ICrossWall proxyServer = new ProxyServer(webServer);
        //开始翻墙
        proxyServer.visitGoogle();
        proxyServer.visitYoutube();
    }

运行结果

静态代理

现在假设想测试下看web服务器访问网页所花费的时间,那么上面的程序就要进行如下修改:

  //被委托类:web服务器
    public class WebServer implements ICrossWall {
        @Override
        public void visitGoogle() {
            long startTime = System.nanoTime();
            System.out.println("real subject:send http request to visit google");
            //模拟访问时间
            try {
                Thread.sleep(130);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long endTime = System.nanoTime();
            System.out.println("cost:" + (endTime - startTime)/Math.pow(10,6) + "ms");
        }

        @Override
        public void visitYoutube() {
            long startTime = System.nanoTime();
            System.out.println("real subject:send http request to visit youtube");
            //模拟访问时间
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long endTime = System.nanoTime();
            System.out.println("cost:" + (endTime - startTime)/Math.pow(10,6) + "ms");
        }
    }

结果

静态代理

从上面的代码中我们可以看出,通过静态代理实现同一附加需求需要在各个方法中都添加相应的逻辑代码,上面示例只有2个方法,手动添加还不算太麻烦,但是如果接口中含有数十上百个方法,手动添加的工作量就大大增加了,并且代码冗余度也大大增加了。那么,有没有什么办法可以优化这个流程呢?答案自然是肯定的,只需通过动态代理动态生成代理类,然后在该代理类内加上相应的计时逻辑代码即可,这样就无需修改每一个方法了,而是在调用相应方法的时候,会执行这些计时逻辑代码。具体实现请看后续部分内容。

动态代理实现

1)接口类:同上
2)被委托类:同上

  //被委托类:web服务器
    public class WebServer implements ICrossWall {
        @Override
        public void visitGoogle() {
            System.out.println("real subject:send http request to visit google");
            //模拟访问时间
            try {
                Thread.sleep(130);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void visitYoutube() {
            System.out.println("real subject:send http request to visit youtube");
            //模拟访问时间
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

3)中介类:InvocationHandler

  //计时拦截器
    public static class CostInterceptor implements InvocationHandler{
        //具体被委托类
        private Object target;
        public CostInterceptor(Object target){
            this.target = target;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            long startTime = System.nanoTime();
            //被委托类原始方法执行
            Object result = method.invoke(target, args);
            long endTime = System.nanoTime();
            System.out.println("cost:" + (endTime - startTime)/Math.pow(10,6) + "ms");
            return result;
        }
    }

4)调用

   public static void main(String[] args) {

        //创建一个被委托类
        ICrossWall webServer = new WebServer();
        //创建一个代理类
        ICrossWall dynamicProxy = (ICrossWall)Proxy.newProxyInstance(
                ICrossWall.class.getClassLoader(),
                webServer.getClass().getInterfaces(),
                new CostInterceptor(webServer));
        //开始翻墙
        dynamicProxy.visitGoogle();
        dynamicProxy.visitYoutube();
    }

结果

动态代理

从上面的动态代理示例可以看到,我们通过动态生成的代理类调用接口方法时,都会执行InvocationHandler内部invoke方法,从而让我们的附加逻辑得以运行。

动态代理实现原理简析:

InvocationHandler(中介类)持有一个被委托类对象引用,然后在内部invoke方法中对被委托类相应方法进行调用,这个实现方式看起来是不是很熟悉-,这不就是我们上面静态代理的实现方式吗!!所以,其实动态代理可以看成是2个静态代理叠加实现:
1.InvocationHandler是具体被委托类的静态代理
2.动态生成的代理是InvocationHandler的代理,InvocationHandler是具体的被委托类
所以,调用链是:DynamicProxy.method()--->InvocationHandler.invoke()--->RealSubject.method()

动态代理内部实现原理:

我们可以通过如下方法获取到动态生成的代理类.class文件,然后通过反编译.class文件就可以看到java为我们动态生成的代理类代码详情:

   /**
     * 保存代理类二进制源码
     * @param name 动态生成的代理类名称
     * @param classes 接口类(动态代理实现的接口类:dynamicProxy implements classes)
     */
    public static void createProxyClassFile(String name, Class<?>[] classes) {
        byte[] data = ProxyGenerator.generateProxyClass(name, classes);
        try {
            FileOutputStream out = new FileOutputStream(name + ".class");
            out.write(data);
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

调用一下上述方法:

 public static void main(String[] args) {
     createProxyClassFile("ProxyICrossWall", new Class[]{ICrossWall.class});
  }

生成的ICrossWall的动态代理源码如下所示:


public final class ProxyICrossWall extends Proxy implements ICrossWall {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m3;
    private static Method m0;

    public ProxyICrossWall(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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 void visitYoutube() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void visitGoogle() throws  {
        try {
            super.h.invoke(this, m3, (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)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m4 = Class.forName("com.example.ProxyDemo$ICrossWall").getMethod("visitYoutube", new Class[0]);
            m3 = Class.forName("com.example.ProxyDemo$ICrossWall").getMethod("visitGoogle", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看出:

  • 动态生成的代理类都是继承自Proxy,并且实现相应接口。
  • 调用动态代理的任何方法最终都会调用InvocationHandler.invoke()方法,包括equals(),toString(),hashCode()等等。
  • 调用链是:DynamicProxy.method()--->InvocationHandler.invoke()--->RealSubject.method()

然后,我们看下Proxy的实现:

  /**
     * Prohibits instantiation.
     */
    private Proxy() {
    }

    /**
     * Constructs a new {@code Proxy} instance from a subclass
     * (typically, a dynamic proxy class) with the specified value
     * for its invocation handler.
     *
     * @param  h the invocation handler for this proxy instance
     *
     * @throws NullPointerException if the given invocation handler, {@code h},
     *         is {@code null}.
     */
    protected Proxy(InvocationHandler h) {
          if (obj == null)
            throw new NullPointerException();
        this.h = h;
    }

可以看到,Proxy的构造函数是private和protected的,所以Proxy是无法直接创建的。所以我们动态生成的代理类都是继承Proxy带InvocationHandler的有参构造函数。

public ProxyICrossWall(InvocationHandler var1) throws  {
        super(var1);
    }

而创建的动态代理是通过Proxy.newProxyInstance()方法生成的,那么我们看下newProxyInstance()源码(经简化,方便理解)

    /** parameter types of a proxy class constructor */
    private static final Class<?>[] constructorParams =
            { InvocationHandler.class };

    /**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
            proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

    /**
     * the invocation handler for this proxy instance.
     * @serial
     */
    protected InvocationHandler h;

  /**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
    **/    
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
            throws IllegalArgumentException
    {
        if (h == null)
            throw new NullPointerException();

        final Class<?>[] intfs = interfaces.clone();
        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            return cons.newInstance(new Object[]{h});
        } 
    }

newProxyInstance会从getProxyClass0()中得到一个代理类类实例(如果代理类之前已经创建过,那么会从proxyClassCache缓存中获取,否则,则创建一个),得到代理类类实例后,通过反射获取带参构造函数对象并生成代理类实例。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 事例 小张是一个普普通通的码农,每天勤勤恳恳地码代码。某天中午小张刚要去吃饭,一个电话打到了他的手机上。“是XX公...
    余平的余_余平的平阅读 493评论 0 0
  • 版权声明:本文为博主原创文章,未经博主允许不得转载 前言 Java 代理模式在 Android 中有很多的应用。比...
    cc荣宣阅读 808评论 0 7
  • 动机 学习动机来源于RxCache,在研究这个库的源码时,被这个库的设计思路吸引了,该库的原理就是通过动态代理和D...
    却把清梅嗅阅读 367评论 0 1
  • 简书 占小狼 转载请注明原创出处,谢谢! 在平时写代码时,经常会用到各种设计模式,其中一种就是代理模式,代理实现可...
    美团Java阅读 10,508评论 19 82
  • 设计模式文章陆续更新 java单例模式java工厂模式java状态模式 这几天在看一些框架源码时看到了一个很奇妙的...
    林锐波阅读 984评论 0 10