一、前言

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

以上内容摘自百度百科。设计模式的重要性相信不用阐述了,在我看来,代码是一门艺术,设计模式则是完成这门艺术的必要骨架,它是一种思想,无关语言。
我是学Java的,对于Java开发者而言有一个“春天”,那就是Spring框架。因其易用高效,极大程度得简化开发而迅速发展;同时Spring源码中也是大量运用设计模式,真正的将设计模式落地,因此也是学习设计模式的宝库。

二、什么是代理模式?

代理模式为一个对象提供一个替身或占位符以控制对这个对象的访问。

这是《Head First 设计模式》对于代理模式的定义,而我最初对于代理模式的认知来自于Spirng。其AOP就依赖于动态代理来实现,也是面试必问题之一了,故而之前也是查阅了很多关于代理模式的资料;但网上的资料千篇一律,都是讲代理的实现,对于它与装饰者模式、适配器模式以及外观模式的区别不甚明了,于初学者而言或许会觉得这些模式都是通过委托来包装对象,借以增加额外的行为,甚至有时会认为它们就是一个模式。但如果你有看过《Head First设计模式》,其对于四者的区别有一个清楚的描述:

代理:包装一个对象,控制对它的访问。
装饰者:包装另一个对象,并提供额外的行为。
适配器:包装另一个对象,并提供不同的接口。
外观:包装许多对象,以简化他们的接口。

假定你已经了解了这三个模式的实现,那么相信看到这里对于它们之间的区别应该很清楚了。抛开抽象的概念,它们之间本质的区别就在于所解决的问题不同,即适用场景的不同,也是真正的体现了设计模式即思想;因此,我们应该从思想的角度去理解实现设计模式,而非从功用上。

回到代理模式,通过定义我们能够明白,代理模式提供被代理对象一个“代表”,用以控制客户对真实对象的访问;既然这个代表要能够“控制”访问,那么它是不是应该持有真实对象的所有资料(即对象的引用)呢?由此,我们可以总结出代理模式的三个特点:

  • 有两个角色参与,代理人与被代理人;
  • 代理人持有被代理人的引用;
  • 被代理人的行为不能或无法直接暴露给客户。

代理模式应用场景非常广,包含且不限于远程代理、虚拟代理、保护代理、缓存代理等非常多的变体,但每一种变体的出现都是为了解决一种实际的问题,且都满足于上诉3个条件。像虚拟代理是为了避免直接访问创建开销大的资源,保护代理是基于权限控制对对象的访问等等,感兴趣的码友们可下去自行研究,接下来我们就从代理对象的创建方式来分析并实现静态代理及动态代理。

三、如何理解并实现?

设计模式的概念是从实际生活中抽象出来,每一个模式在实际生活中都对应很多的实例,因此,若是针对每一个模式都能在现实生活中举出至少三个实例出来,那么对于该模式的理解也就到位了。
前文有提到代理模式必然包含“代理人”及“被代理人”两个角色且客户是无法直接使用被代理人提供的资源,这里以九城代理《魔兽世界》为例。魔兽老玩家应该都还记得当初魔兽代理权由九城换到网易那段时间,期间国内无法直接玩魔兽,需要连接国外服务器才行,但网络延迟非常的高,如果一直是这个情况,对于魔兽本公司暴雪而言必然会损失一大部分的客户,因此,找国内代理公司也就是必然了,也就有了九城和网易代理。但是,代理公司拿到的只是代理权,他能修改其本质吗?如果能,对于暴雪而言不会是一场灾难么?因此,代理人只能在原有的基础上增加自己的特色,这也就涉及到设计模式的六大原则之一了——“对扩展开放,对修改关闭”;虽然魔兽的代理运营权已经交给了九城,但对于玩家来讲,需要改变他原本的行为吗?肯定不需要改变才是最好的,那换到程序上而言,就是客户只需要调用代理人同样的方法,由代理人将请求委托给被代理人来真正执行,代理人可以在此过程中加入自己的逻辑来达到控制访问的目的,那代码中要如何去实现呢?

通过上面的UML图不难理解只需要让代理人与被代理人实现共同的接口和方法就能轻松的实现一个代理模式,首先我们来看静态代理。

(一)静态代理

public interface Game {void operate(String companyName);}// 被代理人
public class BlizzardGame implements Game {@Overridepublic void operate(String compantName) {System.out.println("Game is operating by " + compantName + "!");}}// 代理人
public class NineCity implements Game {private Game game;public NineCity(Game game) {this.game = game;}@Overridepublic void operate(String companyName) {System.out.println("Localizate...");game.operate(companyName);System.out.println("Online activities...");}}public class MainClass {public static void main(String[] args) {Game proxy = new NineCity(new BlizzardGame());proxy.operate(proxy.getClass().getSimpleName());}}

这段代码很简单,不必多说什么,主要就在于“针对接口编程”,同时代理人需要持有被代理人的引用。因其代理类及代理方法都是固定的,所以被称为“静态代理”。看起来非常不错,但是,我们这里只有一个方法,这样写没有什么问题,那如果有很多方法都需要代理,且代理的规则都是一样的呢?再者,如果不止一种代理规则呢?难道需要我们手动一个个地去创建代理类么?那是非常糟糕的一件事情,相信没人会那么干。既然我们不想耗费时间去做重复毫无意义的事情,那要怎样去创建代理呢?这时,动态代理就出现了。

(二)动态代理

动态代理的出现就是为了帮助我们减少重复工作,节省开发时间效率,以及让代码变得更赏心悦目。既然它这么厉害,那究竟是如何实现的呢?在java中动态代理有JDK自带以及CGLIB两种实现方式,首先我们来看JDK自带的方式:

JDK实现及其原理

1. 找代理人

同样的这里先创建一个Subject接口,定义两个方法以示区分静态代理,并让代理人实现此接口:

 public interface Game {void publicize(String companyName);void operate(String companyName);}public class BlizzardGame implements Game {......
}

接下来注意,与静态代理不同的是我们不需要手动创建一个代理类,而是需要写一个Handler来实现InvocationHandler接口,代理类则是由程序运行时动态的生成,这个Handler则可以理解为代理类的辅助类,也就是如何定义代理的规则,调用的所有的代理方法最终都会进入到这里面。

public class MyInvocationHandler implements InvocationHandler {// 被代理人的引用private Game game;public Game getInstance(Game game) {......}// 所有代理的方法最终调用的方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {......}
}

我们创建自己的Handler,并定义getInstance方法,传入被代理人的实例并将其引用赋给成员变量,接着再通过newProxyInstance让JDK自动为们生成以$Proxy0(0是编号,若有多个代理类,即多个Handler则会依次递增)命名的动态代理类保存在内存中(动态的生成类),客户通过调用该方法获取到代理类的实例。

 this.game = game;Class clazz = game.getClass();Game instance = (Game) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);

然后重写InvocationHandler中的invoke方法,也就是定义代理的规则,将共同的逻辑抽离出来实现代码复用(动态的调用方法),即每个代理方法最终都会调用的方法。

// invoke包含了三个参数,proxy就是生成的代理类实例,method是正在执行的代理方法,args则是方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before operate...");// 此处的game不能换成proxy,否则会形成死循环,为什么?先想想,稍后会将Object invoke = method.invoke(game, args);System.out.println("after operate...");return invoke;
}

测试

MyInvocationHandler handler = new MyInvocationHandler();
// Get proxy object
Game instance = handler.getInstance(new BlizzardGame());
// 这里就是动态的调用代理方法
instance.publicize("Nine City");
instance.operate("Nine City");

相信看到这儿对于动态代理是如何帮助我们实现多种代理以及如何代理多个方法已经很清楚了,代码很简单,我们只需要调用JDK的API就行了,而底层复杂的实现方法都由JDK做了,那JDK到底是怎么做的呢?生成的代理类在哪里,长啥样?我们都不知道啊,作为程序员一定要知其然还要知其所以然。

2. 代理人长啥样?

要了解其原理,那么看其源码肯定是最有效的,而动态代理类底层是通过字节码及反射技术生成的并保存在内存中,我们可以通过Proxy本身提供的方法generateProxyClass方法来生成.class文件,再反编译即可看到。

byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{instance.getClass()});
FileOutputStream fo = new FileOutputStream("$Proxy0.class");
fo.write(bytes);
fo.close();

将上面的代码放到测试代码最后则会在根路径下生成一个$Proxy0.class文件,如下:

public final class $Proxy0 extends Proxy implements Proxy0 {public $Proxy0(InvocationHandler var1) throws  {super(var1);}public final void publicize(String var1) throws  {try {super.h.invoke(this, m3, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final void operate(String var1) throws  {try {super.h.invoke(this, m4, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}}

为节省篇幅我删掉了来自于Object的方法。首先我们看其构造方法,我们可以明白newProxyInstance传入的InvocationHandler就是在这里用来初始化Proxy类中的InvocationHandler的。

public class Proxy implements java.io.Serializable {protected InvocationHandler h;

再看两个代理方法,客户端在调用相应方法时其实是由代理类再委托给我们自己写的Handler,最终都是执行其invoke方法,并将代理对象传入进去,那么上文提及的死循环问题相信也都理解为什么出现了,至此我们对于动态代理是如何做到的应该都清楚了,但是代理类是如何生成?又是如何保存到内存中的呢?下面我们就自己来实现一个。

3. 成为创造者

首先创建MyProxy、CustomizeClassloader、CustomizeInvocationHandler三个类替换掉JDK自带的Proxy、Classloader、InvocationHandler:

public class MyProxy {// 这里就是我们自己来生成代理类的逻辑public static Object newProxyInstance(CustomizeClassLoader loader,Class<?> interfaces,CustomizeInvocationHandler h)throws IllegalArgumentException {
// 继承自ClassLoader并重写其findClass方法
public class CustomizeClassLoader extends ClassLoader {
public interface CustomizeInvocationHandler {Object invoke(Object proxy, Method method, Object[] args)throws Throwable;}

创建完成之后我们就需要对newProxyInstance来分析,为什么客户直接调用这个方法就能或取到代理类对象?应该如何去实现?下面是根据网上大牛们的博客进行总结的:

首先我们需要生成$Proxy0中的代码;
其次,创建.java文件,并将第一步生成的源码写到里面;
第三,将.java文件编译为.class文件;
第四,将class加载到jvm;
最后,只需要通过反射生成代理类返回就行了。

这个逻辑是否正确?往下看之前先认真想想有没有什么问题。虽然按照这个套路是可以实现的,但是如果需要生成大量的代理类时,性能会不会存在什么问题?
带着这样的疑问,我将这种实现和JDK源码做了比较,发现JDK底层是通过直接操作字节码来完成的,因此性能上是没有太大问题的,限于小编目前的水平还达不到操作字节码的程度,就按上述逻辑实现了,由于篇幅原因,这里就不过多阐述,具体源码请移步小编github仓库。点击查看

CGLIB实现

讲到这篇幅已经很长了,但在Spring中并不只是使用了JDK动态代理来实现AOP,还有CGLIB实现,那为什么又会出现这种实现方式呢?他们之间有什么区别呢?
要实现CGLIB需要引入cglib包

<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.2.4</version>
</dependency>

然后我们同样创建一个被代理人,此处不需要再实现自接口

// 被代理人
public class BlizzardGame {......}

与JDK一样的是我们肯定也需要代理人与被代理人两个角色,而代理人同样是通过程序生成;不同的是,CGLIB需要我们写一个自己的拦截器实现MethodInterceptor,它的作用其实和InvocationHandler是一样的,也就是辅助创建代理类并定义代理的规则。

public class MyInterceptor implements MethodInterceptor {public Object getInstance(Object obj) {// 代理类生成器Enhancer enhancer = new Enhancer();// 设置代理类的父类,即被代理人enhancer.setSuperclass(obj.getClass());// 设置回调enhancer.setCallback(this);return enhancer.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("before...");// 这里需要调用父类的方法,也就是被代理人的方法,否则会死循环Object o = methodProxy.invokeSuper(obj, args);System.out.println("after...");return o;}
}

代码依然很简单,其生成代理类的原理同JDK生成代理类的原理其实是差不多的,只不过,JDK需要代理人和被代理人都实现同一个接口,而CGLIB则是直接生成被代理人的子类来实现代理。这也就是CGLIB产生的原因,针对没有实现接口的类就可以采用这种方式来实现动态代理。而对于网上所说的CGLIB的效率JDK的10倍,小编在JDK1.8环境下测试过,发现无论如何JDK的效率都是远远高于CGLIB的,我想或许是JDK版本提升后也优化了动态代理的实现吧。感兴趣的可下去自行测试。

四、总结

代理模式实现方式很简单,可以帮助我们解耦合以及控制对象的访问,但也会显然地提升程序处理时间,因此也不要盲目的使用。
至此,结束!小编的Github地址:https://github.com/smile-everyday。欢迎关注评论!最后推荐谷歌的一个插件:Insight.io for github。可以让你在github上浏览代码如IDE中那么方便。下面是效果图:

下载地址:https://pan.baidu.com/s/1O519iW03lf1TpAdDzkISwA 密码:gfu6

设计之禅——深入剖析代理模式相关推荐

  1. Java设计模式圣经连载(05)-代理模式

    代理模式是一种非常重要的设计模式,在Java语言中有着广泛的应用,包括Spring AOP的核心设计思想,都和代理模式有密切关系. 代理模式主要分两种:一种是静态代理,一种是动态代理.两种代理方式的实 ...

  2. 设计模式:代理模式是什么,Spring AOP还和它有关系?

    接着学习设计模式系列,今天讲解的是代理模式. 定义 什么是代理模式? 代理模式,也叫委托模式,其定义是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用.它包含了三个角色: Subject: ...

  3. 设计模式之禅【代理模式】

    真刀实枪之代理模式 我是游戏至尊 "最近几年王者荣耀的热度飙升,自己打时可以体验到其中的升级乐趣,但是时间过得很快啊!自己不想打,找代练,好主意!" 作为一名程序员,先将打游戏这段 ...

  4. 模块递归拆分法: 设计模式 设计原则,复杂层次设计举例。系统重构 装饰模式,门面模式,代理模式

    程序员最牛逼的能力是模块拆分能力, 然后才能利用模块依赖的工具,java 9 或者 runtime期的osgi ,其他maven插件,maven build期. 其他idea插件,类似阿里云的代码规范 ...

  5. 第六周 Java语法总结_设计原则_工厂模式_单例模式_代理模式(静态代理_动态代理)_递归_IO流_网络编程(UDP_TCP)_反射_数据库

    文章目录 20.设计原则 1.工厂模式 2.单例模式 1)饿汉式 2)懒汉式 3.Runtime类 4.代理模式 1)静态代理 2)动态代理 动态代理模板 21.递归 22.IO流 1.File 2. ...

  6. 设计模式之禅-代理模式

    目录 代理模式 例子 定义 优点 使用场景 扩展 普通代理 强制代理 代理的个性-增强 动态代理 代理模式 例子 public interface IGamePlayer {/*** 登录游戏*/vo ...

  7. 《设计模式之禅》——代理模式

    定义如下:provide a surrogate or placeholder for another object to control access to it. 为其他对象提供一种代理以控制对这 ...

  8. 代理模式相关简单论述

    代理模式相关简单论述 1.代理模式的简述 代理模式 (英语:Proxy Pattern)是 程序设计 中的一种 设计模式 . 所谓的代理者是指一个类别可以作为其它东西的接口. 代理者可以作任何东西的接 ...

  9. [设计模式] - 代理模式(静态代理与动态代理)

    文章目录 一.代理模式简介 1. 什么是代理模式 2. 简单举例 二.代理模式的设计思路 1. 代理模式的构成 1. 静态代理 2. 动态代理 (1)接口代理 (2)Cglib代理 三. 代理模式总结 ...

最新文章

  1. 如何在 Mutt 邮件客户端中使用密文密码
  2. 摄影测量(计算机视觉)中的三角化方法
  3. c++ vector pop_back() 与pop_back()
  4. 「安全技术」针对常见混淆技术的反制措施
  5. php js date 格式化,javascript date格式化示例_javascript技巧
  6. Android中的广播Broadcast详解
  7. Git学习的最佳教程
  8. python的array如何使用map_你应该了解的JavaScript Array.map()五种用途小结
  9. 【算法】算法求出2个超大正数相加
  10. C#: Writing a CookieContainer to Disk and Loading Back In For Use
  11. c盘能达到1T吗,为什么?
  12. XP系统中如何查哪些网址曾经远程连接过本机器。
  13. 克隆 Ubuntu 1804后续操作:修改用户名、主机名和组名
  14. 读书笔记-干法-付出不亚于任何人的努力!
  15. Node对象的一些方法
  16. 华为高管丁耘跑28公里后突发疾病去世:在公司工作26年
  17. 数据科学导论实验:基于Twitter的网络结构和社会群体演化
  18. idea git回退到某个历史版本
  19. 视频教程-项目实战:支持以太坊的MySQL管理系统视频课程-区块链
  20. img加载图片失败后处理

热门文章

  1. SQL中in与exist的区别
  2. cached in the local repository的解决办法
  3. 状态模式设计程序:游戏中英雄根据不同的体力值可以进行休息、防御、普通攻击、技能攻击。
  4. Object.assign的用法
  5. 移动物联卡公司哪家好?如何辨别移动物联卡公司是否正规?
  6. x轴z轴代表的方向图片_数控机床的X,Y,Z轴分别指什么方向的运动
  7. 2018.07.17【省赛模拟】模拟B组 比赛总结
  8. 笔试归来,若有所悟(转)
  9. STM32 无线烧录器
  10. JavaSE 学Java语言的前情概要