什么是代理模式?
代理模式是一种比较好理解和实现的模式,简单地说,就是在不改变原来类的的情况下,通过一个代理类去扩展原来类的附加功能。
静态代理
我们先来看一段代码:
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 动态代理的实现,我们可以总结为以下步骤:
- 定义获取订单详情接口 -- OrderDetailsService
- 实现获取订单详情接口 -- OrderDetailsServiceImpl
- 定义一个 JDK 动态代理类 -- DynamicProxyHandler
- 创建代理对象 -- Proxy.newProxyInstance()
代理模式的应用场景
- 业务系统的非功能性需求开发
监控、统计、鉴权、限流、事务、幂等和日志等,我们可以将这些附加功能与业务功能解耦,放到代理类中统一处理,在 Spring 中我们可以放到 AOP 的切面中完成,Spring AOP 底层原理就是基于动态代理。
- 在 RPC 框架和缓存中的应用。
RPC 框架可以看成是一种远程代理,它把网络通信、数据的解码编码等细节都隐藏起来,客户端在使用的时候就好像使用本地函数一样,无需知道细节。对于缓存,我实现了这么一个接口,就是对于帖子列表的请求,可以请求实时的数据也可以是缓存,当我们需要缓存的时候,http://localhost:8080/post/123?cache=true 请求到服务端后,在 AOP 切面中拦截,如果 cache 为 true 就直接返回缓存数据。
参考
《设计模式之美》王争