设计模式之代理模式(从静态代理到动态代理详解)

代理模式

简介

什么是代理模式:
代理模式,也称委托模式,是结构型设计模式之一。是为其他对象提供一种代理以控制这个对象的访问。

适用场景:

  • 当不想访问某个对象或访问某个对象存在困难时,就可以为这个对象创建一个代理,通过代理来间接的访问这个对象;
  • 如果原始对象有不同的访问权限,可以使用代理控制对原始对象的访问,保护原始对象;
  • 在访问原始对象时执行一些自己的附加操作;

区别:
静态代理在我们的代码运行之前,代理类的.class文件就已经存在,而动态代理则与静态代理相反,在代码运行之前不存在代理类的.class文件,在代码运行时才动态的生成代理类。

静态代理

使用静态代理

  • 定义代理对象和真实对象的公共接口;
  • 真实对象实现公共接口中的方法;
  • 代理对象实现公共接口中的方法,并把方法的逻辑转发给真实对象。

以张三买房为例:

张三(真实对象)想买房,但由于太慢就没直接找房东,而是找了一个中间商(代理对象),告诉了中间商他的想法,中间商帮他买到了房,他也支付了中间商一定费用(附加操作)

图解:
在这里插入图片描述

  • 公共租房接口
public interface IRoom {
    void seekRoom();//找房
    void watchRoom();//看房
    void room();//给钱租房
    void finish();//完成租房
}
  • 真实角色
public class ZhangSan implements IRoom{
    @Override
    public void seekRoom() {
        System.out.println("找房");
    }

    @Override
    public void watchRoom() {
        System.out.println("看房");
    }

    @Override
    public void room() {
        System.out.println("给钱租房");
    }

    @Override
    public void finish() {
        System.out.println("完成租房");
    }

}
  • 代理角色
public class Middleman implements IRoom{
    private IRoom mRoom;//持有一个被代理人(张三)的引用

    public Middleman(IRoom room){
        this.mRoom = room;
    }

    @Override
    public void seekRoom() {
        mRoom.seekRoom();
    }

    @Override
    public void watchRoom() {
        mRoom.watchRoom();
    }

    @Override
    public void room() {
        mRoom.room();
    }

    @Override
    public void finish() {
        mRoom.finish();
    }

    /**
     * 附加操作
     */
    public void getMoney(){
        System.out.println("中间商转差价");
    }
}
  • 测试
public class Test {
    public static void main(String[] args) {
        //张三想租房
        ZhangSan zhangSan = new ZhangSan();
        //找一个代理人,房产中介
        Middleman middleman = new Middleman(zhangSan);
        //房产中介找房
        middleman.watchRoom();
        //房产中介看房
        middleman.seekRoom();
        //房产中介租房
        middleman.room();
        //房产中介完成租房
        middleman.finish();
        //附加操作,中间商转差价
        middleman.getMoney();
    }
}

优缺点

优点:

  • 代理类作为客户端和被代理类之间的中介,起到了保护被代理类的作用
  • 通过接口对代理类和被代理类进行解耦,降低了系统的耦合度

缺点:

  • 由于在客户端和被代理类之间增加了代理对象,因此会造成请求的处理速度变慢
  • 如果张三是想要买房而不是租房,这时房产中介还能满足张三的需求吗?很显然不能了,因为这个房产中介它只有替人租房的能力,没有替人买房的能力,这时就需要更换租房接口为买房接口,再定义一个专门买房的的房产中介,你会发现我每次更换接口,都需要更换代理类,这就是静态模式的缺点,只能为给定接口下的实现类做代理,如果接口不同就需要定义不同的代理类,随着系统的复杂度增加,就会很难维护这么多代理类和被代理类之间的关系,这时动态代理就应运而生,当需要频繁的更换接口,更换代理类时,采用动态代理是一个更好的选择,动态代理可以通过一个代理类来代理N多个被代理类,它在更换接口时,不需要重新定义代理类,因为动态代理不需要根据接口提前定义代理类,它把代理类的创建推迟到代码运行时来完成。

动态代理

为了让我们更加容易的实现动态代理,java提供了动态代理接口InvocationHandler和动态代理类Proxy供我们使用,它们都在java.lang.reflect包中,
可见❤️ 动态代理和反射有不可逃脱的关系❤️ 。

InvocationHandler和Proxy

  • InvocationHandler
public interface InvocationHandler {
  /**
   * 这个方法的含义是:代理对象proxy要调用真实对象的method
   * @param proxy 代理对象
   * @param method 真实对象被调用的方法
   * @param args 被调用的方法的参数
   */
  Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}

InvocationHandler接口的作用就是在invoke方法中执行真实对象的方法,可以看到里面只有一个invoke方法,我们需要为真实对象定义一个实现了这个接口中的invoke方法的动态代理类,同时在创建这个动态代理类的实例的时候,我们还要在方法或构造中传入真实对象的引用,即InvocationHandler的实现类需要持有真实对象的引用,这样才能执行真实对象的方法

  • Proxy
public class Proxy implements Serializable {
  
    protected InvocationHandler h;//持有一个InvocationHandler类型的引用

    protected Proxy(InvocationHandler h) {
        this.h = h;
    }

    //根据指定的类加载器和接口来获取代理对象的Class对象
    public static Class<?> getProxyClass(ClassLoader loader, Class... interfaces) throws IllegalArgumentException {
        //...
    }

    //根据指定的类加载器和接口生成代理对象
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
        //...
    }
  
    //...
}

Proxy这个类的作用就是用来动态的创建一个代理对象,它内部会持有一个InvocationHandler引用,在构造中传入,它提供了许多的方法,但是我们常用的是 getProxyClass方法和newProxyInstance方法:

  • getProxyClass(重点方法):这个方法的作用是在运行时根据.class的结构生成一个代理Class二进制流,并通过传入的ClassLoader去把代理Class二进制流加载成一个代理Class对象,该代理Class对象继承Proxy并实现了传入的第二个参数对应的Interface列表。
  • newProxyInstance(常使用的方法): 这个方法的作用是在运行时根据代理Class对象生成代理对象实例,这个方法中会先调用了getProxyClass方法生成代理Class对象,在获取到代理Class对象后,根据第三个参数InvocationHandler引用通过反射创建代理对象实例,所以newProxyInstance最终的结果是生成一个代理对象实例,该代理对象会继承Proxy类并实现给定的接口列表,同时内部持有一个InvocationHandler引用。

使用动态代理

  • 定义代理对象和真实对象的公共接口;
  • 真实对象实现公共接口中的方法;
  • 定义一个实现了InvocationHandler接口的动态代理类
  • 通过Proxy类的newProxyInstance方法创建代理对象,调用代理对象的方法。

张三买房

  • 定义代理对象和真实对象的公共接口
public interface IRoom {
    void seekRoom();//找房
    void watchRoom();//看房
    void room();//给钱租房
    void finish();//完成租房
}
  • 真实对象实现公共接口中的方法
public class ZhangSan implements IRoom{
    @Override
    public void seekRoom() {
        System.out.println("找房");
    }

    @Override
    public void watchRoom() {
        System.out.println("看房");
    }

    @Override
    public void room() {
        System.out.println("给钱租房");
    }

    @Override
    public void finish() {
        System.out.println("完成租房");
    }

}
  • 定义一个实现了InvocationHandler接口的动态代理类
/**
 * 实现了InvocationHandler接口的动态代理类;
 */
public class DynamicProxy implements InvocationHandler {
    private Object mObject;//真实对象的引用

    public DynamicProxy(Object object){
        this.mObject = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object invoke = method.invoke(mObject, args);//反射调用方法:方法名.invoke(对象,参数)
        return invoke;
    }
}
  • 通过Proxy类的newProxyInstance方法创建代理对象,调用代理对象的方法
public class Test {
    public static void main(String[] args) {
        //小明想租房
        ZhangSan zhangSan = new ZhangSan();
        //构造一个动态代理
        DynamicProxy dynamicProxy=new DynamicProxy(zhangSan);
        //获取被代理类小明的ClassLoader
        ClassLoader classLoader = zhangSan.getClass().getClassLoader();
        System.out.println(classLoader);
        //获得一个代理Class对象
        Class<?> proxyClass = Proxy.getProxyClass(classLoader, new Class[]{IRoom.class});

        //生成了代理对象
        IRoom iRoom = (IRoom) Proxy.newProxyInstance(classLoader, new Class[]{IRoom.class}, dynamicProxy);

        //代理对象调用真实对象方法
        iRoom.watchRoom();
        iRoom.seekRoom();
        iRoom.room();
        iRoom.finish();
    }
}

❤️ 注意生成的代理对象被强转为接口

优缺点

优点:

  • 代理类在程序运行时由反射自动生成,无需我们手动编写代理类代码,简化编程工作
  • 一个动态代理类InvocationHandler就能代理多个被代理类,较为灵活

缺点:

  • 利用反射,效率低
  • 只能代理实现了接口的类,而不能代理实现抽象类的类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

眰恦.H

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值