代理模式(Proxy Pattern)

1.介绍

1.1定义

代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。

1.2作用

  • 通过引入代理对象的方式来间接访问目标对象。
  • 防止直接访问目标对象给系统带来的不必要复杂性。

1.3静态代理&动态代理

我们有多种不同的方式来实现代理。如果按照代理创建的时期来进行分类的话, 可以分为两种:静态代理、动态代理。静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。动态代理是在程序运行时通过反射机制动态创建的。

2.模式原理

2.1 UML类图 & 讲解
代理模式UML.png

讲解:

  1. 抽象对象角色:声明了目标类及代理类对象的共同接口,这样在任何可以使用目标对象的地方都可以使用代理对象。
  2. 目标对象角色(真实对象角色):定义了代理对象所代表的目标对象。
  3. 代理对象角色:代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象和目标对象具有统一的接口,以便可以再任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或者之后,执行某些操作,而非单纯的将调用传递给目标对象。
  4. 实现动态代理的关键技术是反射。

3.静态代理

a. 实例概况

  • 背景:小成希望买一台最新的顶配Mac电脑
  • 冲突:国内还没上,只有美国才有
  • 解决方案:寻找代购进行购买

代购(代理对象) 代替 我(真实对象) 去买Mac(间接访问的操作)

b.使用步骤

步骤1: 创建抽象对象接口(AbstractObject):声明你(真实对象)需要让代购(代理对象)帮忙做的事(买Mac)
public interface AbstractObject {
    void buyMac();
}
步骤2: 创建真实对象类(RealObject ),即“我”
public class RealObject implements AbstractObject{
    @Override
    public void buyMac() {
        System.out.println("买一台Mac");
    }
}
步骤3:创建代理对象类(ProxyObject ),即“代购”,并通过代理类创建真实对象实例并访问其方法
public class ProxyObject implements AbstractObject{
    private AbstractObject abstractObject;
    public ProxyObject(AbstractObject abstractObject) {
        this.abstractObject = abstractObject;
    }
    @Override
    public void buyMac() {
        //调用真实对象的方法,进行代理购买Mac
        abstractObject.buyMac();
        //代理对象额外做的操作
        this.WrapMac();
    }
    private void WrapMac() {
        System.out.println("用盒子包装好Mac");
    }
}
步骤4:客户端调用
public class ProxyPattern {
    public static void main(String[] args){
        RealObject realObject = new RealObject();
        ProxyObject proxyObject = new ProxyObject(realObject);
        proxyObject.buyMac();
    }
}
输出结果
买一台Mac
用盒子包装好Mac

c. 优点

  • 协调调用者和被调用者,降低了系统的耦合度;
  • 代理对象作为客户端和目标对象之间的中介,起到了保护目标对象的作用。

d. 缺点

  • 由于在客户端和真实主题之间增加了代理对象,因此会造成请求的处理速度变慢;
  • 实现代理模式需要额外的工作(有些代理模式的实现非常复杂),从而增加了系统实现的复杂度。

4.JDK动态代理

一般来说,对代理模式而言,一个主题类与一个代理类一一对应,这也是静态代理模式的特点。但是,也存在这样的情况,有n各主题类,但是代理类中的“前处理、后处理”都是一样的,仅调用主题不同。也就是说,多个主题类对应一个代理类,共享“前处理,后处理”功能,动态调用所需主题,大大减小了程序规模,这就是动态代理模式的特点。

a. 实例概况

统计行驶时间

步骤1: 抽象主题
public interface Moveable {
    void move() throws Exception;
}
步骤2: 真实主题
import java.util.Random;
public class Car implements Moveable {
    @Override
    public void move() throws Exception {
        Thread.sleep(new Random().nextInt(1000));
        System.out.println("汽车行驶中…");
    }
}
步骤3:事务处理器
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimeHandler implements InvocationHandler {
    private Object target;
    public TimeHandler(Object target) {
        this.target = target;
    }
    /**
     * 参数:
     * @param proxy 被代理的对象
     * @param method 被代理对象的方法
     * @param args 方法的参数
     * @return Object 方法返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long startTime = System.currentTimeMillis();
        System.out.println("汽车开始行驶…");
        method.invoke(target, args);
        long stopTime = System.currentTimeMillis();
        System.out.println("汽车结束行驶…\n汽车行驶时间:" + (stopTime - startTime) + "毫秒!");
        return null;
    }
}
步骤4:测试类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class ProxyPattern {
    public static void main(String[] args){
        try {
            Car car = new Car();
            InvocationHandler h = new TimeHandler(car);
            Class<?> cls = car.getClass();
            /**
             *loader 类加载器(要进行代理的类)
             *interfaces 被代理类实现的接口
             *h InvocationHandler 事务处理器
             */
            Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(), h);
            //执行目标对象的方法,会触发事件处理器的invoke方法
            m.move();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
输出结果
汽车开始行驶…
汽车行驶中…
汽车结束行驶…
汽车行驶时间:44毫秒!

b.优点

  • 不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK再运行时为我们动态的来创建。
  • 大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。

c.缺点(有点懵大概了解下吧,有实力再去看源码深入)

JDK的动态代理类是继承了Proxy类的,所以使用JDK动态代理不能实现继承式动态代理,原因是Java不允许多继承,而生成的代理类本身就已经继承了Proxy类。

5.CGLIB代理(在Spring、Hibernate等很多服务器框架中使用,所以也涉及了很多那方面知识,自己实力不够,仅记录下来做个参考,代码也是跑不通的,会报错)

步骤1:具体主题
public class Train {
    public void move(){
        System.out.println("火车行驶中…");
    }
}
步骤2:生成代理
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();
    public Object getProxy(Class<?> clazz){
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    /**
     * 拦截所有目标类方法的调用
     * 参数:
     * obj目标实例对象
     *method 目标方法的反射对象
     * args方法的参数
     * proxy代理类的实例
     */
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
        //代理类调用父类的方法
        System.out.println("开始…");
        proxy.invokeSuper(obj, args);
        System.out.println("结束…");
        return null;
    }
}
步骤3:测试类
public class ProxyPattern {
    public static void main(String[] args){
        CGLibProxy proxy = new CGLibProxy();
        Train t = (Train) proxy.getProxy(Train.class);
        t.move();
    }
}
步骤4:输出结果
开始…
火车行驶中…
结束…

6.小结

  • 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理。在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样对每一个方法或方法组合进行处理。Proxy 很美很强大,但是仅支持 interface 代理。Java 的单继承机制注定了这些动态代理类们无法实现对 class 的动态代理。好在有cglib为Proxy提供了弥补。class与interface的区别本来就模糊,在java8中更是增加了一些新特性,使得interface越来越接近class,当有一日,java突破了单继承的限制,动态代理将会更加强大。
  • CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。
  • 写点废话,说实话总结这篇博客真的挺懵的,好多都不懂,自己要走的路真的还很长,加油,给自己鼓鼓劲,一定要坚持下去,相信自己,你是最棒的!

7.感谢

代理模式(Proxy Pattern):静态代理 - 最易懂的设计模式解析
Java设计模式——代理模式实现及原理
设计模式---代理模式

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

推荐阅读更多精彩内容