Java 设计模式——代理模式 (Proxy Pattern)

什么是代理模式

客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。为其他对象提供一个代理 以控制对某个对象的访问

  1. 远程代理
  2. 虚拟代理
  3. Copy-on-Write 代理
  4. 保护(Protect or Access)代理
  5. Cache代理
  6. 防火墙(Firewall)代理
  7. 同步化(Synchronization)代理
  8. 智能引用(Smart Reference)代理。等

《Java 设计模式——代理模式 (Proxy Pattern)》

  • AbstractObject【抽象对象角色】 : 声明了目标对象和代理对象的共同接口/父类【动态代理只能是接口】。
  • RealObject【委托对象】 : 代理对象所代表的东西。
  • ProxyObject【代理对象】 : 代理对象内部含有委托对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。
理解代理模式【静态代理】

我们上下班都要坐公交/地铁,那么刷卡或者投币都有不同的优惠,【刷卡9.5折】,投币不打折,这个机器还要判断是否可以通行【余额是否足以支付行程费用】

AbstractObject

abstract class AbstractTicket {

    abstract void checkTicket();
}

RealObject

    class People extends AbstractTicket {
    private int balance;

    int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    People(int balance) {
        this.balance = balance;
    }

    @Override
    void checkTicket() {
        System.out.println("滴,学生卡。卡上余额" + balance +"元");
    }
}

ProxyObject

    class Teller extends AbstractTicket {
    private People people;

    Teller(People people) {
        this.people = people;
    }

    @Override
    void checkTicket() {

        people.checkTicket();
        if (people.getBalance() < 10) {  //权限判断
            System.out.println("卡上余额不足10元,请充满,限行");
            return;
        }

        System.out.println("开闸,放行。");  //还可以根据不同卡的级别进行不同的收费
    }
}
        People people=new People(2);
        Teller teller=new Teller(people);
        teller.checkTicket();


        People maXiaoYun=new People(20000);
        teller=new Teller(maXiaoYun);
        teller.checkTicket();

《Java 设计模式——代理模式 (Proxy Pattern)》

检票过程
《Java 设计模式——代理模式 (Proxy Pattern)》

静态代理模式小结

【代理模式主要是用来权限控制和委托管理。对其他对象提供一种代理以控制对这个对象的访问。】

动态代理

动态代理与静态代理比较

【静态代理】 : 指程序员创建好代理类,编译时直接生成代理类的字节码文件。

  1. 生成委托对象。【同一父类】
  2. 生成代理对象,构造方法关联委托对象。【同一父类】
  3. 通过代理对象调用方法。【内部是一串逻辑判断之后实现委托对象方法】

【动态代理】 : 在程序运行时,通过反射机制动态生成代理类字节码,并加载到JVM中(Java 编译完之后并没有实际的 class 文件)

  1. 生成委托对象。
  2. 创建动态调用处理器,构造方法关联委托对象。【实现InvocationHandler接口的类】
  3. 动态生成代理对象。
  4. 通过代理对象调用方法。

还是通过上面的栗子我们来看看动态代理的实现

委托对象实现的接口

interface AbstractTicket {//此时委托对象必须是实现接口,而不是继承抽象类。
    String checkTicket(String flag); 
}

委托对象

class People implements AbstractTicket {
    private int balance;

    int getBalance() {
        return balance;
    }

    People(int balance) {
        this.balance = balance;
    }

    @Override
    public String checkTicket(String flag) {
        return "滴,学生卡。卡上余额" + balance + "元";
    }
}

调用处理器

class TellerProxy implements InvocationHandler {
    private AbstractTicket target;
    TellerProxy(AbstractTicket target) {
        this.target = target;
    }
    /** * @param proxy 通过Proxy生成的代理类 * @param method 被调用的方法对象 * @param args 调用参数 * 调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行 */
    @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy==" + proxy.getClass());

        System.out.println("args==" + args[0]);

        System.out.println("【请刷卡】");

        if (((People) target).getBalance() < 10) {  //权限判断
            return "卡上余额不足10元,请充满,限行";
        }
        return method.invoke(target, args);
    }

    /** * 返回委托类接口的实例对象 */
    Object getProxyInstance() {
        /** * ClassLoader loader,Class<?>[] interfaces,InvocationHandler h *  @param loader 类加载器对象 *  @param interfaces 代理类需要实现的接口 *  @param h 调用处理器类的实例【即TellerProxy】 */
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
}
        //1.创建委托类的实例,即被代理的对象
        People stuedent = new People(9);
        //2.创建调用处理器
        TellerProxy proxy = new TellerProxy(stuedent);

        //3.动态生成代理对象
        AbstractTicket abstractTicket = (AbstractTicket) proxy.getProxyInstance();
        //4.通过代理对象调用方法
        System.out.println(abstractTicket.checkTicket("我只是个参数"));

《Java 设计模式——代理模式 (Proxy Pattern)》

相关的类和接口【更多请查看developerWorks®】

java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

  • Proxy 的静态方法
// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy) 
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl) 
// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, 
    InvocationHandler h)

java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

  • InvocationHandler 的核心方法【每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象(参见 Proxy 静态方法 4 的第三个参数)。】
// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象
// 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
Object invoke(Object proxy, Method method, Object[] args)

java.lang.ClassLoader:这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。 每次生成动态代理类对象时都需要指定一个类装载器对象(参见 Proxy 静态方法 4 的第一个参数)

动态代理的内部实现【更多请查看xiazdong的简书】

现在我们就会有一个问题: Java 是怎么保证代理对象调用的任何方法都会调用 InvocationHandler 的 invoke() 方法的?

这就涉及到动态代理的内部实现。假设有一个接口 AbstractTicket,且里面有String checkTicket(String flag) 方法,则生成的代理类大致如下:

public final class $Proxy1 extends Proxy implements AbstractTicket{
    private InvocationHandler h;
    private $Proxy1(){}
    public $Proxy1(InvocationHandler h){
        this.h = h;
    }
    public int checkTicket(String flag){
        Method method = AbstractTicket.class.getMethod("checkTicket", new Class[]{int.class});    //创建method对象
        return (String)h.invoke(this, method, new Object[]{new String(flag)}); //调用了invoke方法
    }
}

通过上面的方法就成功调用了 invoke() 方法。

动态代理的总结

首先让我们来了解一下如何使用 Java 动态代理。具体有如下四步骤:

  1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
  2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

动态代理对象创建过程

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(..); 

// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); 

// 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); 

// 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程,所以简化后的过程如下

简化的动态代理对象创建过程

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..); 

// 通过 Proxy 直接创建动态代理类实例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, 
     new Class[] { Interface.class }, 
     handler );

接下来让我们来了解一下 Java 动态代理机制的一些特点。 首先是动态生成的代理类本身的一些特点。1)包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;2)类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;3)类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。4)类继承关系:该类的继承关系如图:

【更多请查看xiazdong的简书】
【更多请查看developerWorks®】

    原文作者:算法小白
    原文地址: https://juejin.im/entry/5938b6abfe88c2006af2a8af
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞