设计模式——代理模式

在阎宏博士的《JAVA与模式》一书中开头是这样描述代理(Proxy)模式的:代理模式是对象的结构模式。代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。

在代理模式(Proxy Pattern)中,由于客户端无法直接或者不想直接引用或使用一个对象,所以通过“中间件”起到代理目标对象功能的作用,为其他对象提供一种代理以控制对这个对象的访问。

代理模式的角色:

  • 抽象主题类(Subject):该类或接口负责定义具体目标类和代理类之间的共同接口方法;
  • 具体主题类(ConcreteSubject):该类也称为被代理类或被委托类,定义了一个真实的对象,最终执行代理类的代理方法中的业务逻辑,客户端通过代理类间接调用真实主题类中定义的方法;
  • 代理类(ProxySubject):该类也被称为委托类或代理类,该类持有一个对真实主题类的引用,在它的方法中调用与真实主题类对应的接口方法;
ProxyPattern.png

Java的三种代理模式

静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。

实例场景

在生活中我们打官司找律师,买房找中介这些场景中都有代理的身影,这里我们以买房子找中介来模拟代理模式。

抽象主题类和具体主题类

/**
 * 定义代理类和被代理的接口
 * @author Iflytek_dsw
 *
 */
interface IUserSubject {
    public void buyHouse();
}

class NormalUser implements IUserSubject{

    @Override
    public void buyHouse() {
        System.out.println("普通人想买房子");
    }
}

创建客户端

public class Client {

    /**
     * @param args
     */
    public static void main(String[] args) {
        IUserSubject userSubject = new NormalUser();
        NormalUserProxy proxy = new NormalUserProxy(userSubject);
        proxy.buyHouse();
    }
}

静态代理总结:

  1. 有点:可以做到在不修改目标对象的功能前提下,对目标功能扩展。
  2. 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多,同时,一旦接口增加方法,目标对象与代理对象都要维护.

静态代理是最简单的代理表现形式,它的UML图跟装饰者模式的比较像,但是表达的含义用途是完全不一样的:

  1. 装饰者模式的核心是动态的扩展功能,是在Decorator类中持有一个具体组件的引用,然后动态扩展具体组件的功能。
  2. 代理模式是代理具体主题的功能,即具体主题把功能实现好,代理只是负责调用,不做补充修改。
  3. 客户端使用都是持有一个抽象组件,然后指向对应的装饰者或代理来进行使用。
动态代理

动态代理是指在运行时动态生成代理类。即代理类的字节码将在运行时生成并载入当前代理的 ClassLoader。在JDK中通过Proxy类进行实现,Proxy类提供了静态的方法用于动态创建代理类和实例。代理实例是代理类的一个实例。 每个代理实例都有一个关联的调用处理程序对象,即实现接口 InvocationHandler的自定义实例对象。

Proxy类
供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。

  • static InvocationHandler getInvocationHandler(Object proxy) :返回指定代理实例的调用处理程序。
  • static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces):返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。
  • static boolean isProxyClass(Class<?> cl):当且仅当指定的类通过 getProxyClass 方法或 newProxyInstance 方法动态生成为代理类时,返回 true。
  • static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。

参数说明

  • ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的
  • Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型
  • InvocationHandler handler:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

InvocationHandler

每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。

动态代理是不需要定义代理角色的,通过一个处理器来处理代理角色的业务逻辑。动态代理有以下特点:

  1. 代理对象,不需要实现接口
  2. 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
  3. 动态代理也叫做:JDK代理,接口代理

实例场景

在数据库连接中,我们都是通过系统封装好的Driver类来进行数据库的操作。这里我以此来模拟一下。

抽象角色接口类

/**
 * 定义接口
 * @author Iflytek_dsw
 *
 */
interface DBDriver{
    public boolean connect();
    public boolean insert();
    public boolean delete();
}

在抽象角色接口类中,我们定义了对应的角色接口。

实现接口,具体角色类

/**
 * 数据库管理
 * @author Iflytek_dsw
 *
 */
class DBDriverManager implements DBDriver{
    
    @Override
    public boolean connect(){
        System.out.println("实际操作者:连接数据库");
        return true;
    }
    
    @Override
    public boolean insert(){
        System.out.println("实际操作者:插入数据库");
        return true;
    }

    @Override
    public boolean delete() {
        System.out.println("实际操作者:删除数据库");
        return true;
    }
}

代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。动态就体现在用接口来动态指向上。

定义处理器对象

class MyinvocationHandler implements InvocationHandler{
    
    private DBDriver dbDriver;
    
    public MyinvocationHandler(DBDriver dbDriver){
        this.dbDriver = dbDriver;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        String methodName = method.getName();
        if("connect".equals(methodName)){
            System.out.println("通过代理调用方法:" + methodName);
            dbDriver.connect();
        }else if("insert".equals(methodName)){
            System.out.println("通过代理调用方法:" + methodName);
            dbDriver.connect();
        }else if("delete".equals(methodName)){
            System.out.println("通过代理调用方法:" + methodName);
            dbDriver.connect();
        }
        return Boolean.TRUE;
    }
}

定义一个工厂方法用于创建Proxy类

这里有两种创建方式:

  1. 直接通过newProxyInstance方法进行创建;
  2. 通过getProxyClass获取对应的Class对象,然后在通过反射获取对应的实例,绑定处理器完成。
class DynamicProxyFactory{
    
    public static Object getProxyInstance(Object object){
        
        Class proxy = Proxy.getProxyClass(object.getClass().getClassLoader(), 
                object.getClass().getInterfaces());
        try {
            //通过反射获取构造函数,然后构建处理器
            return proxy.getConstructor(new Class[] { InvocationHandler.class })
            .newInstance(new MyinvocationHandler((DBDriver)object));
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public static Object getProxyInstance2(Object object){
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), 
                object.getClass().getInterfaces(), new MyinvocationHandler((DBDriver)object));
    }
}

客户端使用

public class Client {

    /**
     * @param args
     */
    public static void main(String[] args) {
        DBDriver dbDriver = new DBDriverManager();
        DBDriver flextProxy = (DBDriver) DynamicProxyFactory.getProxyInstance(dbDriver);
        flextProxy.connect();
        flextProxy.insert();
        System.out.println("简化方式\n");
        DBDriver simpleProxy = (DBDriver) DynamicProxyFactory.getProxyInstance2(dbDriver);
        simpleProxy.connect();
        simpleProxy.delete();
        
    }
}

结果

通过代理调用方法:connect
实际操作者:连接数据库
通过代理调用方法:insert
实际操作者:连接数据库
简化方式

通过代理调用方法:connect
实际操作者:连接数据库
通过代理调用方法:delete
实际操作者:连接数据库

动态代理和静态代理对比

  1. 不需要为真实主题写一个形式上完全一样的代理类,假如主题接口中的方法很多,为每一个接口写一个代理方法也很麻烦。如果接口有变动,则真实主题和代理类都要修改,不利于系统维护。
  2. 使用一些动态代理的生成方法甚至可以在运行时制定代理类的执行逻辑,从而大大提升系统的灵活性。比如过滤数据。

动态代理类使用字节码动态生成加载技术,在运行时生成加载类。生成动态代理类的方法很多,如,JDK 自带的动态处理、CGLIB、Javassist 或者 ASM 库。JDK 的动态代理使用简单,它内置在 JDK 中,因此不需要引入第三方 Jar 包,但相对功能比较弱。CGLIB 和 Javassist 都是高级的字节码生成库,总体性能比 JDK 自带的动态代理好,而且功能十分强大。ASM 是低级的字节码生成工具,使用 ASM 已经近乎于在使用 Java bytecode 编程,对开发人员要求最高,当然,也是性能最好的一种动态代理生成工具。但 ASM 的使用很繁琐,而且性能也没有数量级的提升,与 CGLIB 等高级字节码生成工具相比,ASM 程序的维护性较差,如果不是在对性能有苛刻要求的场合,还是推荐 CGLIB 或者 Javassist。(引用自【周明耀-代理模式原理及实例讲解】)

代理模式的使用场景

远程代理
为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。使服务器端Server实现对客户端的隐藏,以便Server可以忽略服务器端。

虚拟代理
是根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象,并在需要的时候进行加载。

保护代理
通过代理模式来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限的时候;

只能引用
在访问原始对象时执行一些额外的附加操作并对指向原始数据计数。

总结

设计模式是前人的工作总结经验,灵活运用,针对某一类型的教优解决方案。

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

推荐阅读更多精彩内容

  • 一、概述   代理模式我们接触的就比较多了,所谓的代理模式就是,给某一个对象提供一个代理对象,并由代理对象控制对原...
    骑着乌龟去看海阅读 864评论 0 9
  • 1. 定义 为其他对象提供一种代理以控制对这个对象的访问。 2. 使用场景 当想对某个对象做功能增强拓展,但又不想...
    程序员修仙阅读 365评论 1 2
  • 目录 本文的结构如下: 引言 什么是代理模式 模式的结构 典型代码 代理模式分类 代码示例 代理模式和装饰者模式的...
    w1992wishes阅读 1,493评论 0 13
  • 题记:偶读人民网《城区道路以克论净…辞退环卫工》一文,有感,乃为之文。 主题:《既以克论地净否,以斤看官是否贪?》...
    河边茅草阅读 152评论 0 2
  • 我是容易着急上火发脾气的类型,并且属于那种往往事后后悔型。看过很多心灵鸡汤类的文章,教如何控制自己的脾气,发现事...
    湘心灵听阅读 307评论 0 0