java代理模式-原来你是这样的代理

设计模式文章陆续更新

java单例模式
java工厂模式
java状态模式

这几天在看一些框架源码时看到了一个很奇妙的设计模式,有种熟悉个感觉,一时想不出是什么模式,后面经过了解才知道是动态代理,就这样带着好奇心学习了这个模式,更深入了解代理会发现不仅有静态和动态,还有很多其他的代理类别,果然兴趣是最好的老师,效率不错,下面是我一些总结.

一起来体验下,你也会发现,原来你是这样的代理.

这里写图片描述

什么是代理?

在<大话设计模式>中说到,代理模式,为其他对象提供一种代理以控制对这个对象的访问.

下面通过一个例子,说明下.

商家需要搞活动,请了陈奕迅过来商演唱歌,那么商家需要跟陈奕迅进行面谈->签合同->首付款->安排行程->唱歌->收尾款.

  • 在不使用代理的情况下是这样的.

    这里写图片描述

    可以看到陈奕迅好像很忙,除了要唱歌还要做很多的交互,这样我的爱豆不是要忙死了.

  • 使用代理模式

    这里写图片描述

    通过代理人来处理一些琐碎的事情后,陈奕迅就只要负责他独有的功能唱歌就行了,这样间接的访问对象,有效的减轻了一些重复的操作.

代理的核心角色

我们可以将代理模式分为三大角色:

  • 抽象角色
    • 代理角色真实角色的公共方法都会在这里定义.
  • 真实角色
    • 供给代理角色用,这里实现了抽象角色的业务逻辑.
    • 关注真正的业务逻辑
  • 代理角色
    • 真实角色的代理,根据真实角色的业务逻辑来实现
      抽象角色的抽象方法,并可以附件自己的操作.

java中的代理

代理模式(Proxy)是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题.
它也是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.

在java中动态代理机制以巧妙的方式实现了代理模式的设计理念,因此java中也分为动态代理静态代理.

  • 静态代理(静态定义代理类)
  • 动态代理(动态生成代理类)
    • 反射机制,JDK自带的动态代理
    • 通过InvocationHandler(处理器接口)
      • 使用invoke方法实现对真实角色的代理访问.
      • 每次通过Proxy生产代理类对象时,都要指定的处理器对象.
    • java动态性这块可以深入看看,反射与javassist

静态代理(StaticProxy)

借用Eason-S大神的类图,静态代理UML类图

这里写图片描述

talk is cheap,根据上面陈奕迅的案例来写个demo

  • 抽象角色
/*
 * 明星和代理人的公共接口
 */
public interface StarInterface {

    // 面谈
    void interview();

    // 签合同
    void signContract();

    // 首付款
    void firstPayment();

    // 安排行程
    void plan();

    // 唱歌
    void sing();

    //收尾款
    void finalPayment();
}
  • 真实角色
/*
 * 明星实现类
 */
public class EasonStar implements StarInterface {

    private final String STAR_NAME = "陈奕迅:";

    @Override
    public void sing() {
        System.out.println(STAR_NAME+"唱歌");
    }

    @Override
    public void finalPayment() {
        System.out.println(STAR_NAME+"收尾款");
    }

    @Override
    public void signContract() {
        System.out.println(STAR_NAME+"签合同");
    }

    @Override
    public void plan() {
        System.out.println(STAR_NAME+"安排行程");
    }

    @Override
    public void interview() {
        System.out.println(STAR_NAME+"面谈");

    }

    @Override
    public void firstPayment() {
        System.out.println(STAR_NAME+"预付款");
    }
}
  • 代理角色
public class ProxyStar implements StarInterface {
    //私有化被代理角色
    private StarInterface star;
    private final String PROXY_NAME = "代理人:";
    
    /*
     * 使用接口的方式来指向真实角色(多态特性)
     */
    public ProxyStar(StarInterface star) {
        super();
        this.star = star;
    }

    @Override
    public void sing() {
        // 唱歌是明星的特有方法,代理是没有的,因此需要明星自己来
        // 调用陈奕迅的唱歌方法...
        star.sing();
    }

    @Override
    public void finalPayment() {
        System.out.println(PROXY_NAME+"签尾款");
    }

    @Override
    public void signContract() {
        System.out.println(PROXY_NAME+"签合同");
    }

    @Override
    public void plan() {
        System.out.println(PROXY_NAME+"安排行程");
    }

    @Override
    public void interview() {
        System.out.println(PROXY_NAME+"面谈");
    }

    @Override
    public void firstPayment() {
        System.out.println(PROXY_NAME+"预付款");
    }
}
  • 客户端类
public class Client {

    public static void main(String[] args) {
        //找到陈奕迅
        EasonStar realStar = new EasonStar();
        //找到代理人,专门为陈奕迅代理
        ProxyStar proxyStar = new ProxyStar(realStar);
        //代理人来完成
        proxyStar.interview();
        proxyStar.signContract();
        proxyStar.firstPayment();
        proxyStar.plan();
        //这里调用的是 EasonStar的sing方法
        proxyStar.sing();
        proxyStar.finalPayment();
    }
}

输出结果:

代理人:面谈
代理人:签合同
代理人:预付款
代理人:安排行程
陈奕迅:唱歌
代理人:签尾款

从上面静态代理demo中,你会发现无论代理角色真实角色都需要实现接口,并且将真实角色的细节向调用方完全隐藏,可以看下EasonStar类里面有很多方法,但最终被掉用的只有sing(),其他都是代理角色来调用的.

动态代理(DynamicProxy)

动态代理的思维模式与之前的一般模式是一样的,也是面向接口进行编码,创建代理类将具体类隐藏解耦,不同之处在于代理类的创建时机不同,动态代理需要在运行时因需实时创建.

这里写图片描述

看下代码.
由于抽象角色真实角色的代码跟上面静态代理是一样的这里就直接给出代理角色和客户端类的代码.

  • 代理角色
/**
 * 动态代理角色 主要代理真实角色的方法,
 * 被调用的方法都会走 invoke()方法,可以在该方法中处理真实角色的业务逻辑
 * 
 * @author relicemxd
 *
 */
public class StarHandler implements InvocationHandler {
   //私有的被代理角色
    private Object star;
    private final String PROXY_NAME = "代理人:";

    /*
     * Obj 传入的是需要被代理的真实角色
     * 并且通过下面的反射技术获取到要代理的行为
     */
    public StarHandler(Object star) {
        super();
        this.star = star;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // 所有掉代理类的方法都会走这里

        // TODO 1. 在转调具体目标对象之前,可以执行一些预处理逻辑
        System.out.println(PROXY_NAME + "面谈");
        System.out.println(PROXY_NAME + "签合同");
        System.out.println(PROXY_NAME + "预付款");
        System.out.println(PROXY_NAME + "安排行程");

        Object invoke = null;
        // 因为proxy每调用的方法都会走这里, 因此就可以通过 invoke的特性来做一些逻辑判断
        if (method.getName().equals("sing")) {
            // TODO 2. 转调具体目标对象的方法
            // 只有当代理对象调用到了sing的方法,才进入
            invoke = method.invoke(star, args);
        }
        // TODO 3.在转调具体目标对象之后,可以执行一些后处理逻辑
        System.out.println(PROXY_NAME + "收尾款");

        return invoke;
    }
}
  • 客户端类
public class Client {
    public static void main(String[] args) {
        // 陈奕迅准备要找代理(真实角色)
        EasonStar star = new EasonStar();
        // 找到了这个代理人代理(代理角色)
        StarHandler handler = new StarHandler(star);

        // jdk提供的代理实例
        StarInterface proxyStar = (StarInterface) Proxy.newProxyInstance(
                ClassLoader.getSystemClassLoader(), // 类加载器
                new Class[] { StarInterface.class }, // 这里必须是接口,否则会报错
                handler);// 代理类

        // 调用interview 会进入StarHandler类的`invoke方法`,
        // 但是interview方法不会被执行
        // proxyStar.interview();
        // 这里调用的是 EasonStar的sing方法
        proxyStar.sing();
        // proxyStar.finalPayment();
    }
}

通过动态代理可以看出对抽象角色我们无需知道他是什么时候创建的,也不用知道改接口实现了什么,并且实现的是InvocationHandler接口,接口的唯一方法invoke()用于处理真实角色的逻辑.

静态代理vs动态代理?

可以从代理的接口和创建过程来分析下他们的不同之处,进一步了解下代理模式.

代理中的接口

  • 共同之处
  • 都会在代理角色中会创建一个私有的成员变量
  • 都需要通过接口来实现代理,主要利用java多态的特性
  • 不同之处
    • 静态代理的真实角色代理角色都会实现同一接口(抽象角色),动态代理则只有真实角色实现了接口.
    • 静态代理利用了java的多态特性来实现代理模式,而动态代理巧妙的使用了jdk的反射机制来完成代理,也因此两者区别在于代理角色的实现方式不一样,看下面这条描述.
    • 静态代理的代理角色实现的是抽象角色这个接口,而动态代理实现的是jdk的内置接口InvocationHandler.

创建代理的过程

  • 共同之处

    • 两者的创建原理一致,需要通过创建代理角色来处理真实角色的一些业务逻辑.
  • 不同之处

    • 静态代理可以直接编码创建,而动态代理是利用反射机制来抽象出代理类的创建过程.
    • 静态代理我们知根知底,要对哪个接口、哪个实现类来创建代理类,所以我们在编译前就直接实现与实现类相同的接口,直接在实现的方法中调用实现类中的相应(同名)方法即可;而动态代理不同,我们不知道它什么时候创建,也不知道要创建针对哪个接口、实现类的代理类(因为它是在运行时因需实时创建的).
    • 在客户端中静态代理利用接口的多态来调用被代理方法,而动态代理则比较复杂通过Proxy.newProxyInstance来创建一个代理实例从而进行代理.这里有人会问使用的不是反射吗?也没跟接口有关联,其实同样也是使用了多态.使用接口指向代理类的实例,最后会用该实例来进行具体方法的调用即可.

优缺点是什么?

优点:

  • 拓展新好
    动态代理,不需要更改原有的代码,能在运行过程中根据接口的类型动态的调用真实角色,符合开闭原则.

  • 解耦
    代理角色可以说是一个中介,隔离了客户端和真实角色.

缺点:

  • 代码量大
    静态代理,需要接口和类,有比较多的重复代码,降低了维护性.

  • 编译效率?
    动态代理,使用的是反射机制相对效率会降低,但实际差别如何,见下面测试代码.

  • 代码可读性
    都是对于接口实现进行代理,因此代码的可读性都不会很好.

两者的效率如何?

下面我对代理sing()方法进行了代理测试.

public class Client {
    public static void main(String[] args) throws Exception {
        // 陈奕迅准备要找代理(真实角色)
        EasonStar star = new EasonStar();

        long start = System.currentTimeMillis();

        int threadNum = 10;
        CountDownLatch latch = new CountDownLatch(threadNum);

        for (int i = 0; i < threadNum; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        dynamicProy(star);// 163

                        // staticProxy(star);//96
                    }
                    latch.countDown();
                }
            }).start();
        }

        latch.await();

        long end = System.currentTimeMillis();

        System.out.println("总共耗时:" + (end - start));
    }

    public static void dynamicProy(EasonStar star) {
        // 找到了这个代理人代理(代理角色)
        StarHandler handler = new StarHandler(star);
        // jdk提供的代理实例
        StarInterface proxyStar = (StarInterface) Proxy.newProxyInstance(
                ClassLoader.getSystemClassLoader(), // 类加载器
                new Class[] { StarInterface.class }, // 这里必须是接口,否则会报错
                handler);// 代理类

        proxyStar.sing();
    }

    public static void staticProxy(EasonStar star) {
        // 找到代理人,专门为陈奕迅代理
        ProxyStar proxyStar = new ProxyStar(star);
        proxyStar.sing();
    }
}

输出结果:

dynamicProy(star);// 163ms
staticProxy(star);// 96ms

静态代理的效率稍微会比动态代理快一些,不过也没有差别很大,因此在选择代理模式类别时,最好还是根据项目需求来筛选出合适的代理模式.

代理的应用场景?

那么代理在哪里会用到呢?如果你的项目有这几个方面的需求可以考虑使用.

•   当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理,例如一个对象需要很长时间才能完成加载时。
•   当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理。通过使用缓冲代理,系统无须在客户端每一次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。
•   当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理。
•   当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理。

代理的分类

当然除了我最常用的静态,动态代理之外根据代理的实现与目标不同还可以分成下面几种代理,具体见其他代理模式

  • 安全代理:
    屏蔽对真实角色的直接访问.
  • 远程代理:
    通过代理角色远程方法调用(RMI)
  • 延迟加载:
    先加载轻量级代理角色,真正需要再加载真实角色.
  • 虚拟代理
    允许内存开销较大的对象在需要的时候创建.只有我们真正需要这个对象的时候才创建.
  • 保护代理
    为不同的客户提供不同级别的目标对象访问权限.

如,你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文洁时不能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开.

项目中有没使用过呢?

在android中目前很热门的一个网路工具retrofit源码中也使用了动态代理.

public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) 

//这里是不是是曾相识呢
Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();
          @Override public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            return loadMethodHandler(method).invoke(args);
          }
        });
}

参考:
java静态代理与动态代理

公共技术点之 Java 动态代理

设计模式(结构型)之代理模式

每天设计模式-代理模式

静态代理VS动态代理

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

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,292评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • 有人说感情经得起风雨,却经不起平淡,其实想想也的确如此。感情一旦走过了初恋时的新奇,热恋时的激情,就剩下了平淡。那...
    D035牛牛_佛山阅读 182评论 3 7