静态代理与动态代理

103 阅读5分钟

什么是代理模式?

代理模式是一种比较好理解和实现的模式,简单地说,就是在不改变原来类的的情况下,通过一个代理类去扩展原来类的附加功能。

静态代理

我们先来看一段代码:

public interface OrderDetailsService {
    OrderDetailsVo getOrderDetails(String orderNumber);
}
public class OrderDetailsServiceImpl implements OrderDetailsService {

    @Autowired
    private RequestService requestService;

    public OrderDetailsVo getOrderDetails(String orderNumber) {
        long startTime = System.currentTimeMillis();
        // 获得订单详情业务代码(略)
        long endTime = System.currentTimeMillis();
        long responseTime = endTime - startTime;
        // 统计接口调用
        RequestInfo requestInfo = new RequestInfo();
        requestInfo.setName("getOrderDetails");
        requestInfo.setStartTime(startTime);
        requestInfo.setResponseTime(responseTime);
        requestService.recordRequest(requestInfo);
        // 返回 OrderDetailsVo 数据
        return orderDetailsVo;
    }
}

上面这段代码有两个很明显的问题,第一就是性能计数的代码侵入到业务代码中,跟业务代码耦合,这样是不好的,当我们需要替换这个计数代码的时候,那将是一个耗时耗力的工作。第二就是统计接口请求的代码和业务代码无关,业务类要职责更单一,只负责业务。那么为了将这些代码代码和业务代码解耦合,我们可以使用代理模式。实现一个代理类 OrderDetailsProxy 并实现 OrderDetailsService,OrderDetailsServiceImpl 只负责实现业务代码,OrderDetailsProxy 负责在业务代码前后附加其他逻辑代码,并通过委托的的方式调用 OrderDetailsService 来执行业务代码。

public class OrderDetailsProxy implements OrderDetailsService {
    
    private RequestService requestService;

    private OrderDetailsService orderDetailsService;

    public OrderDetailsProxy(OrderDetailsService orderDetailsService) {
        this.orderDetailsService = orderDetailsService;
    }

    @Override
    public OrderDetailsVo getOrderDetails(String orderNumber) {
        long startTime = System.currentTimeMillis();
        // 委托
        OrderDetailsVo orderDetails = orderDetailsService.getOrderDetails(orderNumber);
        long endTime = System.currentTimeMillis();
        long responseTime = endTime - startTime;
        // 统计接口调用
        RequestInfo requestInfo = new RequestInfo();
        requestInfo.setName("getOrderDetails");
        requestInfo.setStartTime(startTime);
        requestInfo.setResponseTime(responseTime);
        requestService.recordRequest(requestInfo);
        return orderDetails;
    }
}
public class Main {

    public static void main(String[] args) {
        OrderDetailsServiceImpl orderDetailsService = new OrderDetailsServiceImpl();
        OrderDetailsProxy orderDetailsProxy = new OrderDetailsProxy(orderDetailsService);
        OrderDetailsVo orderDetails = orderDetailsProxy.getOrderDetails("123");
        System.out.println(orderDetails);
    }
}

在上面代理模式实现的中,我们可以看到原始类和代理类都需要实现相同的接口,但是如果原始类没有定义接口,也就是说可能原始类不是我们自己写的,而是第三方库的,我们没有办法去修改他的代码,对于这种外部类的扩展,我们可以采用继承的形式,代码如下:

public class OrderDetailsProxy extends OrderDetailsServiceImpl {

    private RequestService requestService;

    public OrderDetailsProxy() {
        this.requestService = new RequestServiceImpl();
    }

    @Override
    public OrderDetailsVo getOrderDetails(String orderNumber) {
        long startTime = System.currentTimeMillis();
        // 委托
        OrderDetailsVo orderDetails = super.getOrderDetails(orderNumber);
        long endTime = System.currentTimeMillis();
        long responseTime = endTime - startTime;
        // 统计接口调用
        RequestInfo requestInfo = new RequestInfo();
        requestInfo.setName("getOrderDetails");
        requestInfo.setStartTime(startTime);
        requestInfo.setResponseTime(responseTime);
        requestService.recordRequest(requestInfo);
        return orderDetails;
    }
}
public class Main {

    public static void main(String[] args) {
        OrderDetailsProxy orderDetailsProxy = new OrderDetailsProxy();
        OrderDetailsVo orderDetails = orderDetailsProxy.getOrderDetails("123");
        System.out.println(orderDetails);
    }
}

以上的修改结果是静态代理模式,在静态代理中,我们需要在代理类中,将原始类的所有方法都重新实现一遍,并且为代码附加相似的逻辑,而且如果原始类不止一个,我们需要为每个原始类都创建一个代理类,100 个原始类我们就需要创建 100 个代理类,导致项目臃肿,增加了代码的维护成本,从 JVM 层面来说,静态代理在编译时,为原始类、代理类、实现的接口都编译成了一个个的 class 文件。

动态代理

我们可以使用动态代理解决上述问题,使用动态代理,我们不用事先为每个原始类实现代理类,而是在运行的时候,动态地为原始类创建代理类,然后在系统中用代理类替换原始类,从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

如何实现动态代理?在 Java 中,实现动态代理很简单,因为 Java 已经给我们提供了动态代理的语法,实际上依赖于 Java 的反射机制,我们可以使用 JDK 动态代理或者 CGLIB 动态代理。

public class RequestCollectorProxy {

    private RequestService requestService;

    public RequestCollectorProxy() {
        this.requestService = new RequestServiceImpl();
    }

    public Object createProxy(Object proxyObject) {
        Class<?>[] interfaces = proxyObject.getClass().getInterfaces();
        DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler(proxyObject);
        return Proxy.newProxyInstance(proxyObject.getClass().getClassLoader(), interfaces, dynamicProxyHandler);
    }

    private class DynamicProxyHandler implements InvocationHandler {

        // 真实对象
        private Object proxyObject;

        public DynamicProxyHandler(Object proxyObject) {
            this.proxyObject = proxyObject;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            long startTime = System.currentTimeMillis();
            // 委托
            Object invoke = method.invoke(proxyObject, args);
            long endTime = System.currentTimeMillis();
            long responseTime = endTime - startTime;
            // 统计接口调用
            RequestInfo requestInfo = new RequestInfo();
            String apiName = proxyObject.getClass().getName() + ":" + method.getName();
            requestInfo.setName(apiName);
            requestInfo.setStartTime(startTime);
            requestInfo.setResponseTime(responseTime);
            requestService.recordRequest(requestInfo);
            return invoke;
        }
    }
}
public class Main {

    public static void main(String[] args) {
        RequestCollectorProxy proxy = new RequestCollectorProxy();
        OrderDetailsService orderDetailsService = (OrderDetailsService) proxy.createProxy(new OrderDetailsServiceImpl());
        OrderDetailsVo orderDetails = orderDetailsService.getOrderDetails("123");
        System.out.println(orderDetails);
    }
}

以上是 JDK 动态代理的实现,我们可以总结为以下步骤:

  1. 定义获取订单详情接口 -- OrderDetailsService
  2. 实现获取订单详情接口 -- OrderDetailsServiceImpl
  3. 定义一个 JDK 动态代理类 -- DynamicProxyHandler
  4. 创建代理对象 -- Proxy.newProxyInstance()

代理模式的应用场景

  1. 业务系统的非功能性需求开发

监控、统计、鉴权、限流、事务、幂等和日志等,我们可以将这些附加功能与业务功能解耦,放到代理类中统一处理,在 Spring 中我们可以放到 AOP 的切面中完成,Spring AOP 底层原理就是基于动态代理。

  1. 在 RPC 框架和缓存中的应用。

RPC 框架可以看成是一种远程代理,它把网络通信、数据的解码编码等细节都隐藏起来,客户端在使用的时候就好像使用本地函数一样,无需知道细节。对于缓存,我实现了这么一个接口,就是对于帖子列表的请求,可以请求实时的数据也可以是缓存,当我们需要缓存的时候,http://localhost:8080/post/123?cache=true 请求到服务端后,在 AOP 切面中拦截,如果 cache 为 true 就直接返回缓存数据。

参考

《设计模式之美》王争