简介

代理模式旨在不修改原始代码的前提下,为其拓展附加功能

通过引入一个代理对象来控制对原始对象的访问,代理对象充当中介的角色,可以在访问原始对象的前后进行额外的处理。

举个例子:

  • 我们点外卖时,只需要在App上下单,等待骑手把外卖送到我们手上。
  • 商家收到订单后,只需要按照要求制作,给到骑手配送。
  • 骑手接到单子,前往商家取餐,配送给客户,这中间一系列的流程由App代理完成,客户与商家都不需要关心。

静态代理

实现

代理模式主要涉及以下几个角色:

  • 抽象主题(Subject):定义真实主题和代理的公共接口。
  • 真实主题(Real Subject):实现了抽象主题接口,是代理对象所代表的真实对象,执行核心的业务逻辑。
  • 代理(Proxy):同样实现了抽象主题接口,并引用真实主题,通常在核心业务逻辑的基础上提供额外的功能。
  • 客户端(Client):使用抽象主题的接口操作真实主题或代理,不需要关心内部细节。

还是以上面点外卖的例子来讲解,首先要有点单服务接口:

public interface OrderService {
    public boolean order(String name);
}

商家收到订单后,要处理订单(实现服务接口,核心业务逻辑):

public class OrderServiceImpl implements OrderService {
    @Override
    public boolean order(String name) {
        System.out.println(name + " 下单成功");
        return true;
    }
}

代理需要在用户与商家之间完成预备工作与收尾工作:

public class OrderServiceProxy implements OrderService{
    @Override
    public boolean order(String name) {
        // 预备工作
        System.out.println("商品:" + name);
        System.out.println("生成订单中.....");
        System.out.println("订单生成成功。");

        // 引用核心业务逻辑
        OrderService orderService = new OrderServiceImpl();
        boolean status = orderService.order(name);

        // 收尾工作
        if (status) {
            System.out.println("等待商家制作....");
        }else {
            System.out.println("下单失败。");
        }
        return status;
    }
}

客户可以直接通过代理点餐,不关心内部细节:

public class Consumer {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceProxy();
        boolean status = orderService.order("123");
        System.out.println(status);
    }
}

缺点

静态代理实现了抽象主题接口,只能为给定接口的实现类做代理,针对不同的接口需要手动实现不同的代理类。

由于在 JVM 运行前接口、实现类、代理类都会被编译为一个个实际的 .class 文件,因此称为静态代理。

随着业务逐渐复杂,需要不断的增加代理类,难以维护真实主题与代理之间的关系。

动态代理

相比静态代理,动态代理更加灵活,不需要针对某个类或某个接口来实现。

在 JVM 的角度,代码在运行前并不存在代理类的 .class 文件,而是在运行时通过反射机制动态生成并加载到 JVM 中

JDK 中提供了创建动态代理的类 Proxy ,其中使用频率最高的方法是 newProxyInstance()

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)

该方法有3个参数

  • loader:用于指定用哪个类加载器,去加载生成的代理类
  • interfaces:指定接口,这些接口用于指定生成的代理类长什么样,也就是有哪些方法
  • h:用来指定生成的代理对象要干什么事情

要实现动态代理,必须传递实现了 InvocationHandler 接口的对象。当动态代理对象调用任意方法时,会转发到实现了 InvocationHandler 接口的对象下的 invoke 方法,在该方法中自定义代理的业务逻辑。

public interface InvocationHandler{
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

invoke 方法也有3个参数

  • proxy:动态代理生成的代理类对象
  • method:代理类正在调用的方法
  • args:调用当前 method 时,携带的参数数组

实现

现在,将上面已经实现的静态代理改成动态代理。首先,动态代理类需要实现 InvocationHandler 接口,并重写 invoke 方法。

为了能够在 invoke 方法中调用真实主题所对应的方法,代理类中还需要记录被代理的实现类对象。

public class ServiceProxy implements InvocationHandler {

    private Object target;

    ServiceProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 预备工作
        System.out.print("商品:");
        for (var a : args) {
            System.out.println(" " + a);
        }
        System.out.println("生成订单中.....");
        System.out.println("订单生成成功。");

        // 引用核心业务逻辑
        boolean status = (boolean) method.invoke(target, args);

        // 收尾工作
        if (status) {
            System.out.println("等待商家制作....");
            return status;
        }else {
            System.out.println("下单失败。");
        }
        return null;
    }
}

使用一个动态代理工厂来根据实现类创建实际的代理对象

public class ProxyFactory {
    public static <T> T getProxy(Object implClass) {
        return (T) Proxy.newProxyInstance(
                implClass.getClass().getClassLoader(),
                implClass.getClass().getInterfaces(),
                new ServiceProxy(implClass)
        );
    }
}

用户通过动态代理工厂获取代理对象,不关心代理细节

public class Consumer {
    public static void main(String[] args) {
        OrderService orderService = ProxyFactory.getProxy(new OrderServiceImpl());
        boolean status = orderService.order("123");
        System.out.println(status);
    }
}

结语

静态代理的代码非常简单,就是在实现类前面增加一个代理类,在代理类中实现拓展业务同时引用实现类中的核心业务。

静态代理的缺点也很明显:

  • 随着业务的增加,每一个接口都需要单独的创建代理类和实现类。
  • 若其中任何一个接口发生变化,对应的代理类和实现类都需要进行调整。

动态代理通过反射在运行时动态创建代理对象,不必针对每个接口都单独创建代理类。

动态代理写产品业务时几乎用不到,但是在应用框架中被广泛使用,例如 Spring AOP 的核心思想就是动态代理。