什么是代理模式?

它在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能

我们通过一个简单的例子来解释一下这段话。

这个例子来自我们在第 25、26、39、40 节中讲的性能计数器。当时我们开发了一个 MetricsCollector 类,用来收集接口请求的原始数据,比如访问时间、处理时长等。在业务系统中,我们采用如下方式来使用这个 MetricsCollector 类


public class UserController {//...省略其他属性和方法...private MetricsCollector metricsCollector; // 依赖注入public UserVo login(String telephone, String password) {long startTimestamp = System.currentTimeMillis();// ... 省略login逻辑...long endTimeStamp = System.currentTimeMillis();long responseTime = endTimeStamp - startTimestamp;RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);metricsCollector.recordRequest(requestInfo);//...返回UserVo数据...}public UserVo register(String telephone, String password) {long startTimestamp = System.currentTimeMillis();// ... 省略register逻辑...long endTimeStamp = System.currentTimeMillis();long responseTime = endTimeStamp - startTimestamp;RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);metricsCollector.recordRequest(requestInfo);//...返回UserVo数据...}
}

很明显,上面的写法有两个问题。
第一,性能计数器框架代码侵入到业务代码中,跟业务代码高度耦合。如果未来需要替换这个框架,那替换的成本会比较大。

第二,收集接口请求的代码跟业务代码无关,本就不应该放到一个类中。业务类最好职责更加单一,只聚焦业务处理。

为了将框架代码和业务代码解耦,代理模式就派上用场了。

  • 代理类 UserControllerProxy原始类 UserController 实现相同的接口 IUserController
  • UserController 类只负责业务功能。
  • 代理类 UserControllerProxy 负责在业务代码执行前后附加其他逻辑代码,并通过委托的方式调用原始类来执行业务代码。

具体的代码实现如下所示:


public interface IUserController {UserVo login(String telephone, String password);UserVo register(String telephone, String password);
}public class UserController implements IUserController {//...省略其他属性和方法...@Overridepublic UserVo login(String telephone, String password) {//...省略login逻辑...//...返回UserVo数据...}@Overridepublic UserVo register(String telephone, String password) {//...省略register逻辑...//...返回UserVo数据...}
}public class UserControllerProxy implements IUserController {private MetricsCollector metricsCollector;private UserController userController;public UserControllerProxy(UserController userController) {this.userController = userController;this.metricsCollector = new MetricsCollector();}@Overridepublic UserVo login(String telephone, String password) {long startTimestamp = System.currentTimeMillis();// 委托UserVo userVo = userController.login(telephone, password);long endTimeStamp = System.currentTimeMillis();long responseTime = endTimeStamp - startTimestamp;RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);metricsCollector.recordRequest(requestInfo);return userVo;}@Overridepublic UserVo register(String telephone, String password) {long startTimestamp = System.currentTimeMillis();UserVo userVo = userController.register(telephone, password);long endTimeStamp = System.currentTimeMillis();long responseTime = endTimeStamp - startTimestamp;RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);metricsCollector.recordRequest(requestInfo);return userVo;}
}//UserControllerProxy使用举例
//因为原始类和代理类实现相同的接口,是基于接口而非实现编程
//将UserController类对象替换为UserControllerProxy类对象,不需要改动太多代码
IUserController userController = new UserControllerProxy(new UserController());

参照基于接口而非实现编程的设计思想,将原始类对象替换为代理类对象的时候,为了让代码改动尽量少,在刚刚的代理模式的代码实现中,代理类和原始类需要实现相同的接口

但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的(比如它来自一个第三方的类库),我们也没办法直接修改原始类,给它重新定义一个接口。在这种情况下,我们该如何实现代理模式呢?

对于这种外部类的扩展,我们一般都是采用继承的方式。这里也不例外。我们让代理类继承原始类,然后扩展附加功能。原理很简单,不需要过多解释,你直接看代码就能明白。具体代码如下所示:


public class UserControllerProxy extends UserController {private MetricsCollector metricsCollector;public UserControllerProxy() {this.metricsCollector = new MetricsCollector();}public UserVo login(String telephone, String password) {long startTimestamp = System.currentTimeMillis();UserVo userVo = super.login(telephone, password);long endTimeStamp = System.currentTimeMillis();long responseTime = endTimeStamp - startTimestamp;RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp);metricsCollector.recordRequest(requestInfo);return userVo;}public UserVo register(String telephone, String password) {long startTimestamp = System.currentTimeMillis();UserVo userVo = super.register(telephone, password);long endTimeStamp = System.currentTimeMillis();long responseTime = endTimeStamp - startTimestamp;RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp);metricsCollector.recordRequest(requestInfo);return userVo;}
}
//UserControllerProxy使用举例
UserController userController = new UserControllerProxy();

动态代理的原理解析

不过,刚刚的代码实现还是有点问题。

一方面,我们需要在代理类中,将原始类中的所有的方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑。

另一方面,如果要添加的附加功能的类有不止一个,我们需要针对每个类都创建一个代理类。

如果有 50 个要添加附加功能的原始类,那我们就要创建 50 个对应的代理类。这会导致项目中类的个数成倍增加,增加了代码维护成本。并且,每个代理类中的代码都有点像模板式的“重复”代码,也增加了不必要的开发成本。那这个问题怎么解决呢?

我们可以使用动态代理来解决这个问题。所谓动态代理(Dynamic Proxy),就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。那如何实现动态代理呢?

如果你熟悉的是 Java 语言,实现动态代理就是件很简单的事情。因为 Java 语言本身就已经提供了动态代理的语法(实际上,动态代理底层依赖的就是 Java 的反射语法)。我们来看一下,如何用 Java 的动态代理来实现刚刚的功能。具体的代码如下所示。其中,MetricsCollectorProxy 作为一个动态代理类,动态地给每个需要收集接口请求信息的类创建代理类。


public class MetricsCollectorProxy {private MetricsCollector metricsCollector;public MetricsCollectorProxy() {this.metricsCollector = new MetricsCollector();}public Object createProxy(Object proxiedObject) {Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();DynamicProxyHandler handler = new DynamicProxyHandler(proxiedObject);return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler);}private class DynamicProxyHandler implements InvocationHandler {private Object proxiedObject;public DynamicProxyHandler(Object proxiedObject) {this.proxiedObject = proxiedObject;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long startTimestamp = System.currentTimeMillis();Object result = method.invoke(proxiedObject, args);long endTimeStamp = System.currentTimeMillis();long responseTime = endTimeStamp - startTimestamp;String apiName = proxiedObject.getClass().getName() + ":" + method.getName();RequestInfo requestInfo = new RequestInfo(apiName, responseTime, startTimestamp);metricsCollector.recordRequest(requestInfo);return result;}}
}//MetricsCollectorProxy使用举例
MetricsCollectorProxy proxy = new MetricsCollectorProxy();
IUserController userController = (IUserController) proxy.createProxy(new UserController());

实际上,Spring AOP 底层的实现原理就是基于动态代理。用户配置好需要给哪些类创建代理,并定义好在执行原始类的业务代码前后执行哪些附加功能。Spring 为这些类创建动态代理对象,并在 JVM 中替代原始类对象。原本在代码中执行的原始类的方法,被换作执行代理类的方法,也就实现了给原始类添加附加功能的目的。

总结

  • 不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法
  • 代理类可以扩展原有类的功能
  • 代理模式的实现可以通过组合或继承

思考

  • 为什么不用原始类? (别人的代码不要随便改)

  • 除了 Java 语言之外,在你熟悉的其他语言中,如何实现动态代理呢?

  • 今天讲了两种代理模式的实现方法,一种是基于组合,一种基于继承,请对比一下两者的优缺点。

  • 什么是动态代理?

  • 如何实现动态代理?

参考

48 | 代理模式:代理在RPC、缓存、监控等场景中的应用

Java的三种代理模式 - 洋葱源码 - 博客园

代理模式的原理解析入门相关推荐

  1. 北风设计模式课程---深入理解[代理模式]原理与技术

    北风设计模式课程---深入理解[代理模式]原理与技术 一.总结 一句话总结: 不仅要通过视频学,还要看别的博客里面的介绍,搜讲解,搜作用,搜实例 设计模式都是对生活的抽象,比如用户获得装备,我可以先装 ...

  2. 代理模式源码解析(jdk+spring+mybatis)

    首先是java.lang.reflect,也就是我们刚刚使用的Proxy这个类,这里面coding的时候,也就是debug的时候,这个就是代理的一个典型应用,还有proxyFactoryBean,这个 ...

  3. 代理模式详解(包含原理详解)

    http://www.cnblogs.com/zuoxiaolong/p/pattern3.html 作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为 ...

  4. (二)代理模式详解(包含原理详解)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处. 我特意将本系列改了下名字,原名是<设计模式学习之路>,原因是因为之前写过一篇<spring源码学习之路>,但是 ...

  5. 设计模式(十三) 代理模式和Java动态代理

    Proxy Pattern 1. 什么是代理模式 代理模式: 给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问. Proxy Pattern: Provide a surrogate ...

  6. 代理模式真得这么简单

    文章目录 代理模式真得这么简单 代理模式类图 静态代理实例 动态代理实例 java动态代理类图 案例 CGLIB代理实例 CGLIB原理解析 代理模式真得这么简单 代理模式的定义:为另一个对象提供一个 ...

  7. 代理模式(静态、动态)

    文章目录 一.代理模式 1.什么是代理模式 2.为什么使用代理模式 3.代理模式应用场景 4..代理模式实现原理 5.代理模式创建的方式 6.静态代理 6.1基于接口方式 6.1基于继承方式 7.动态 ...

  8. Android插件化原理解析——Hook机制之动态代理

    使用代理机制进行API Hook进而达到方法增强是框架的常用手段,比如J2EE框架Spring通过动态代理优雅地实现了AOP编程,极大地提升了Web开发效率:同样,插件框架也广泛使用了代理机制来增强系 ...

  9. flask web开发是前端还是后端_Flask Web开发实战:入门、进阶与原理解析 PDF 全格式版...

    给大家带来的一篇关于Flask相关的电子书资源,介绍了关于Flask.Web.开发实战方面的内容,本书是由机械工业出版社出版,格式为PDF,资源大小12.2M,李辉编写,目前豆瓣.亚马逊.当当.京东等 ...

最新文章

  1. 为了探究不同光照处理_浅谈中考物理实验探究易错题
  2. 投票|PMCAFF产品经理第一课,下一站去哪?杭州、深圳、上海...
  3. 拼多多和国美合作,这可不仅仅是可转债的问题
  4. MYSQL 表锁情况查看
  5. Eclipse中的Github Gists
  6. nginx静态文件服务器_Linux分享文件?快速创建静态文件服务器
  7. mysql半连接_MySQL优化案例:半连接(semi join)优化方式导致的查询性能低下(转载)...
  8. 使用git管理自己的代码--简单使用流程
  9. 关于hibernate的一些小记
  10. java开发flex_FLEX+Java开发
  11. oracle11g-RHEL5.8-Kernel Parameters and Resource Limits
  12. 45.MySQL Cluster
  13. spring boot入门之——2.0新特性以及模块化构建
  14. 从B树、B+树、B*树谈到R树
  15. java开源项目网站社区_一些开源项目网址
  16. WBS(工作分解结构)
  17. OpenCV OAK-D-S2相机测试
  18. 整合营销系统推荐乐云seo_珠海整合营销【乐云seo】
  19. 失去池子的笑果文化越来越不好笑了
  20. 【每日早报】2019/12/31

热门文章

  1. python提供的内置函数有哪些_python内置函数介绍
  2. as安装过程中gradle_电磁阀在安装过程中需注意的一些细节
  3. cuda out of memory gpu还有空间_《室内设计》光与空间的无缝衔接
  4. java负零_java数据结构从零基础到负基础
  5. python图层_Python叠加矩形框图层2种方法及效果
  6. python语言的记事本在哪_用python语言编写一个简单记事本
  7. 9. 设计二个函数分别计算sinx和cosx_Excel技巧篇(一):超级好用的几个函数
  8. html不同类别的列表设置,HTML --列表
  9. 什么影响matlab损失值,matlab – 在计算Logistic损失函数的值和梯度时避免数值溢出...
  10. Oracle 备份shell,oracle数据库shell备份脚本