代理模式(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设计模式——代理模式实现及原理
设计模式---代理模式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容