Design Pattern
1、概述
2、创建型模式
2.1 单例模式
2.2 工厂模式
3、结构型模式
3.1 代理模式
3.2 装饰器模式
4、行为型模式
4.1 策略模式
-
+
游客
注册
登录
代理模式
## 1 含义 1. 代理模式是指**通过代理控制对象的访问**,可以**在这个对象调用方法之前**、**调用方法之后去处理/添加新的功能**。 2. **代码在原有代码乃至原业务流程都不修改的情况下**,**直接在业务流程中切入新代码**,**增加新功能**。 > 关于代理,以下小例子可以帮助我们理解代理的含义: > > 1. 游戏代练 > 游戏代练这件事就是一个代理模式,所谓代练,就是 `Proxy`,也即代理类,代理的流程是我们把自己的账号交给代练人员,让他们帮我们打怪升级,而我们只需提供账号即可,代练人员那边,他所要做的就是登陆我们的账号,然后替我们打游戏,从第三者的角度看,我们这个角色在打怪升级,但这个第三者并不知道是不是我们本人在打游戏,他只能看到我们这个账号正在打怪升级,但并不需要知道后面打游戏的是谁,这就是代理模式,由他人代理玩游戏。 > 2. 邀请明星 > 假设我们现在要邀请明星来上节目,我们应该先给他的经纪人打电话,然后再由经纪人通知到该明星,这里经纪人充当的就是代理的角色。 ## 2 应用场景 1. 当**不想访问某个对象**或**访问某个对象存在困难**时,就可以为这个对象创建一个代理,**通过代理来间接的访问这个对象**。 2. 如果**原始对象有不同的访问权限**,可以使用代理**控制对原始对象的访问**,**保护原始对象**。 3. **在访问原始对象时执行一些自己的附加条件**。 4. **为某个对象在不同的地址空间提供局部代理**,**使得系统可以将服务端的实现隐藏**,**客户端不必考虑服务端的存在**。 5. 具体应用场景主要包括**Spring AOP**、**日志打印**、**异常处理**、**事务控制**、**权限控制**。 ## 3 分类 代理模式主要分为**静态代理模式**和**动态代理模式**两类,其中**动态代理模式可分为 `JDK` 动态代理和 `cglib` 动态代理两种**。 ### 3.1 静态代理 #### 3.1.1 含义 1. 静态代理是**由程序员或工具生成代理类的源码**,再**编译代理类**。 2. 所谓静态也就是**在程序运行之前就已经存在代理类的字节码文件**,**代理类和委托类的关系在运行前就确定了**。 #### 3.1.2 类图 ![](/media/202107/2021-07-11_1552360.40255556430193207.png) 1. `Subject`:**抽象主题类**,**定义了代理对象和真实对象的共同接口方法**,既**可以是接口**,**也可以是抽象类**。 2. `RealSubject`:**真实主题类**,该类**可以称为被委托类或被代理类**,该类**定义了代理对象所表示的真实对象**,**实现了 `Subject` 接口**,而 `Client`**端通过代理类间接的调用真实主题类的方法**,由其**执行真实的业务逻辑**。 3. `ProxySubject`:**代理类**,该类也被**称为委托类或代理类**,该类中**持有一个真实主题类的引用**,同样**实现了 `Subject` 接口**,**在其实现的接口方法中调用真实主题类中相应的接口方法**,**以此起到代理的作用**。 4. `Client`:**客户端**,**使用代理**。 > 相关近义词: > > 1. Subject = 公共接口。 > 2. ProxySubject = 代理对象 = 代理类 = 委托类 = 代理人。 > 3. RealSubject = 真实对象 = 被代理类 = 被委托类 = 被代理人。 #### 3.1.3 实例 > 使用静态代理的基本步骤为: > > 1. **定义代理对象和真实对象的公共接口**。 > 2. **真实对象实现公共接口中的方法**。 > 3. **代理对象实现公共接口中的方法**,并**把方法的逻辑转发给真实对象**。 我们通过小明买房的例子来讲解一下静态代理,小明想要在大城市租房,但是他平时很忙没有时间去看房,于是他就找到一个房产中介,把自己的租房意愿告诉房产中介,让房产中介来替自己解决租房问题,很明显房产中介就是代理人,小明就是被代理的人,下面我们用静态代理来实现这个过程: 1. 首先定义一个租房步骤的公共接口: ```java public interface IRoom { void seekRoom(); // 找房 void watchRoom(); // 看房 void room(); // 给钱租房 void finish(); // 完成租房 } ``` 4 个步骤完成租房。 2. 然后我们定义具体的想要租房的人,即小明: ```java public class XiaoMing implements IRoom { @Override public void seekRoom() { System.out.println("找房"); } @Override public void watchRoom() { System.out.println("看房"); } @Override public void room() { System.out.println("给钱租房"); } @Override public void finish() { System.out.println("完成租房"); } } ``` 3. 小明这个类实现了 `IRoom` 接口,实现了其中的具体逻辑,但是小明并会自己去租房,他委托房产中介去做,因此我们需要定义一个房产中介: ```java public class RoomAgency implements IRoom { private IRoom mRoom; // 持有一个被代理人(小明)的引用 public RoomAgency(final IRoom mRoom) { this.mRoom = mRoom; } @Override public void seekRoom() { mRoom.seekRoom(); } @Override public void watchRoom() { mRoom.watchRoom(); } @Override public void room() { mRoom.room(); } @Override public void finish() { mRoom.finish(); } } ``` 在该类中会持有一个被代理人的引用,在这里指小明,可以看到房产中介所执行的方法实际上就是简单的调用被代理人中的方法。 4. 下面我们定义一个客户端: ```java public class Client { public static void main(String[] args) { // 小明想租房 XiaoMing xiaoMing = new XiaoMing(); // 找一个代理人 RoomAgency roomAgency = new RoomAgency(xiaoMing); // 房产中介找房 roomAgency.seekRoom(); // 房产中介看房 roomAgency.watchRoom(); // 房产中介租房 roomAgency.room(); // 房产中介完成租房 roomAgency.finish(); } } ``` 5. 在上面的例子中,房产中介代理了小明的找房、看房、租房等过程,可以看到静态代理模式其实就是一种委托机制,真实对象将方法委托给代理对象,而且房产中介还可以继续代理其他人,比如 XiaoHong 也想租房,我们再定义一个 XiaoHong 实现 IRoom 接口,并在 `Client` 中给房产中介 `RoomAgency` 代理就行。 #### 3.1.4 优缺点 ##### 3.1.4.1 缺点 1. **只能为给定接口下的实现类做代理**,**如果接口不同**,**就需要定义不同的代理**,例如上面小明是想要买房而不是租房,这是现在的房产中介就不能满足小明的需求了,因为现在的房产中介只有替人租房的能力,没有替人买房的能力,这时就需要更换租房接口为买房接口,再定义一个专门买房的房产中介,我们可以发现,每次更换接口,都需要更换代理类。 ### 3.2 动态代理 动态代理模式可分为 `JDK` 动态代理和 `cglib` 动态代理两种。 #### 3.2.1 JDK 动态代理 ##### 3.2.1.1 含义 1. JDK 动态代理是**利用 JDK 的 API**,**动态的在内存中构建代理对象**(**根据被代理的接口来动态生成代理类的 `class` 文件**,**并加载运行的过程**)。 ##### 3.2.1.2 类图 ![](/media/202107/2021-07-11_1706050.10019961593686455.png) 1. 上面就是动态代理的大概类图结构,其中 `Subject`、`ProxySubject`、`RealSubject` 和 `Client` 角色的作用和静态代理的一样。 2. 与静态代理相比,多了一个 `InvocationHandler` 角色和一个 `Proxy` 角色: 1. `InvocationHandler` 是 `Java` 提供的一个接口,我们需要**定义一个类实现 `InvocationHandler` 接口**,这里就叫 `DynamicProxy` 角色。 2. `Proxy` 是 `Java` 提供用于**动态生成 `ProxySubject`** 的一个类,它**需要 `ProxySubject` 继承**。 3. `DynamicProxy` 在 `ProxySubject` 和 `RealSubject` 之间起到了**中间人**的角色,`ProxySubject` 会把事情委托给 `DynamicProxy` 来做,而 `DynamicProxy`**最终把事情委托给 `RealSubject` 来做**,其中最重要的一点是 `ProxySubject`**是在代码运行时才动态生成的**,**这是和静态代理的最大区别**。 ##### 3.2.1.3 实例 > 使用 JDK 动态代理的基本步骤为: > > 1. **定义代理对象和真实对象的公共接口**(与静态代理步骤相同)。 > 2. **真实对象实现公共接口中的方法**(与静态代理步骤相同)。 > 3. **定义一个实现了 `InvocationHandler` 接口的动态代理类**。 > 4. **通过 `Proxy` 类的 `newProxyInstance` 方法创建代理对象**,**调用代理对象的方法**。 > > 与静态代理的区别是: > > 1. **少了一个步骤**: > 1. **代理对象实现公共接口的方法**,因为动态代理的代理对象是代码运行时通过 `Proxy` 动态创建的,所以不需要提前编写代理对象的类。 > 2. **多了两个步骤**: > 1. **定义一个实现了 `InvocationHandler` 接口的动态代理类**。 > 2. **通过 `Proxy` 类的 `newProxyInstance` 方法创建代理对象**,**调用代理对象的方法**。 1. 我们需要定义一个动态代理类,他用于执行真实对象的方法: ```java // 实现了 InvocationHandler 的动态代理类 public class DynamicProxy implements InvocationHandler { private Object mObject; // 真实对象引用 public DynamicProxy(final Object mObject) { this.mObject = mObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 通过反射调用真实对象的方法 Object result = method.invoke(mObject, args); return result; } } ``` 在该类中,我们声明了一个 `Object` 引用,该引用指向真实对象,真实对象在构造函数中传入,而**在 `invoke()` 方法中**,**通过反射调用真实对象的具体方法**,这里需要注意的是指向真实对象的引用类型最好定义为 `Object` 类型而不是真实对象的具体类型,如 `XiaoMing`,这样做的好处是当我们需要代理另外一个人时,例如 `XiaoHong`,我们只需在 `DynamicProxy` 的构造函数中传入 `XiaoHong` 引用而不用更改 `DynamicProxy` 的类结构,这样一个 `DynamicProxy` 就可以代理很多人。 2. 通过 `Proxy` 类的 `newProxyInstance()` 方法创建代理对象,调用代理对象的方法: ```java public class Client { public static void main(String[] args) { // 构建一个小明 XiaoMing xiaoMing = new XiaoMing(); // 构造一个动态代理 DynamicProxy dynamicProxy = new DynamicProxy(xiaoMing); // 获取被代理类 小明 的 ClassLoader ClassLoader classLoader = xiaoMing.getClass().getClassLoader(); // 1. 通过 Proxy 类的 newProxyInstance 方法动态构造一个代理人房产中介 IRoom roomAgency = (IRoom) Proxy.newProxyInstance(classLoader, new Class[]{IRoom.class}, dynamicProxy); // 2. 调用代理对象的方法 // 房产中介找房 roomAgency.seekRoom(); // 房产中介看房 roomAgency.watchRoom(); // 房产中介租房 roomAgency.room(); // 房产中介完成租房 roomAgency.finish(); } } ``` **`Proxy` 的 `newProxyInstance()` 方法会根据传入的类加载器动态生成代理对象实例**,**生成的代理对象会继承 `Proxy` 类并实现传入的接口列表**: 1. 这里的**类加载器是小明的 `ClassLoader`**,即**真实对象的类加载器**。 2. **接口列表则是 `IRoom`**,**传入的为 `IRoom` 的 `Class` 对象**。 3. 除了这两个参数,还**传入了动态代理类 `InvocationHandler` 实例**,这样 `Proxy`**类在创建代理对象的实例时就会把这个 `InvocationHandler` 引用传给代理对象**,接下来**当我们调用代理对象的方法时**,**这个方法的处理逻辑就会委托给 `InvocationHandler` 实例的 `invoke()` 方法执行**,`invoke()`**方法中就会通过反射调用我们真实对象的方法**。 ##### 3.2.1.4 源码分析 我们先看 `Client` 的注释 1: ```java // 1. 通过 Proxy 类的 newProxyInstance 方法动态构造一个代理人房产中介 IRoom roomAgency = (IRoom) Proxy.newProxyInstance(classLoader, new Class[]{IRoom.class}, dynamicProxy); ``` 1. `Proxy` 的 `newProxyInstance()` 方法会**根据传入的类加载器动态生成代理对象的实例**,我们可以看一下 `Proxy` 的 `newProxyInstance()` 方法的源码: ```java //Proxy.java private static final Class<?>[] constructorParams = { InvocationHandler.class }; public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException{ Objects.requireNonNull(h); //clone 一下传入的接口列表 final Class<?>[] intfs = interfaces.clone(); //getProxyClass 会把逻辑转发给 getProxyClass0,所以 getProxyClass 的作用 = getProxyClass0 的作用,它们的区别只是一个是 public,一个是 private 的 //1、调用 getProxyClass0,获得一个代理 Class 对象 Class<?> cl = getProxyClass0(loader, intfs); try { //constructorParams = InvocationHandler.class //2、这里通过代理 Class 对象获取构造参数为 InvocationHandler 的 Constructor final Constructor<?> cons = cl.getConstructor(constructorParams); //传入的 InvocationHandler 引用 final InvocationHandler ih = h; //这个 Constructor 是 protected 的,所以要设置为 Public if (!Modifier.isPublic(cl.getModifiers())) { cons.setAccessible(true); } //3、通过构造参数为 InvocationHandler 的 Constructor 反射创建代理对象实例,并传入 InvocationHandler 引用给构造 return cons.newInstance(new Object[]{h}); } //...省略异常处理 } ``` 1. 这个方法里面的流程还是很简单的,首先注释 1,调用 `getProxyClass0()`,获得一个代理 `Class` 对象,`getProxyClass0` 等于 `getProxyClass` 的作用,主要用于**在运行时根据 `.class` 的结构生成一个代理 `Class` 二进制流**,并**通过传入的 `ClassLoader` 去把代理 `Class` 二进制流加载成一个 `Class` 对象**,**该代理 `Class` 对象继承 `Proxy` 并实现了传入的第二个参数对应的 `Interface` 列表**。 ```java public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)throws IllegalArgumentException { final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } return getProxyClass0(loader, intfs); } ``` 2. 我们来看一下这个动态生成的代理 `Class` 对象的真实面目: 1. 首先在 `Client` 的 `main` 函数的开头填入下面的一段代码: ```java // jdk8 及之前: System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); // jdk8 之后: System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true"); ``` 2. 填入后,运行 `Client` 的 `main` 函数,会在 `idea` 工作空间下的 `com/sun/proxy/` 目录下生成一个 `$Proxy0.class` 文件,这个文件就是动态生成的代理 `Class` 对象,即代理对象,这个 `.class` 文件里面都是 JVM 才能看懂的二进制,用 `idea` 打开时,它会自动帮我们反编译成 `.java` 文件,具体内容如下: ```java public final class $Proxy0 extends Proxy implements IRoom{ private static Method m1; private static Method m3; private static Method m4; private static Method m2; private static Method m5; private static Method m6; private static Method m0; //调用父类 Proxy 的构造函数,传入 InvocationHandler 引用 public $Proxy0(InvocationHandler paramInvocationHandler){ super(paramInvocationHandler); } //下面四个方法都是实现自 IRoom 的方法,可以看到它们只是简单的调用了父类的 h 的 invoke 方法,并把代理对象 $Proxy0 实例、要调用的方法 method,还有参数传了进去 public final void watchRoom(){ try{ this.h.invoke(this, m3, null); return; } //...省略异常处理 } public final void room(){ try{ this.h.invoke(this, m4, null); return; } //...省略异常处理 } public final void seekRoom(){ try{ this.h.invoke(this, m5, null); return; } //...省略异常处理 } public final void finish(){ try{ this.h.invoke(this, m6, null); return; } //...省略异常处理 } //...我们只关注 IRoom 接口中的方法,所以我省略了 Object 中继承而来的 toSting,hashcode 方法等,里面逻辑都一样,都是调用父类的 h 的 invoke 方法 static{ try{ m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); //获取 IRoom 接口方法的 Method 对象 m3 = Class.forName("com.example.hy.designpatternDemo.proxy.IRoom").getMethod("watchRoom", new Class[0]); m4 = Class.forName("com.example.hy.designpatternDemo.proxy.IRoom").getMethod("room", new Class[0]); m5 = Class.forName("com.example.hy.designpatternDemo.proxy.IRoom").getMethod("seekRoom", new Class[0]); m6 = Class.forName("com.example.hy.designpatternDemo.proxy.IRoom").getMethod("finish", new Class[0]); return; } //...省略异常处理 } } ``` 3. 可以看到 `Proxy` 类的 `getProxyClass0()` 方法会替我们动态生成代理对象 `$Proxy0.class`,这个代理对象会继承 `Proxy` 类和实现接口列表,而这里传入的接口只有 `IRoom`,因此 `$Proxy0` 会实现 `IRoom` 的方法,这些方法里面的逻辑都是调用父类的 `h` 的 `invoke()` 方法,父类的 `h` 就是 `InvocationHandler` 的引用,是在通过反射创建 `$Proxy0` 实例时在构造中传入的。 4. 我们在 `$Proxy0` 中还发现了很多 `Method` 对象,**在 `$Proxy0` 的底部的 `static` 块中通过反射获取到我们 `IRoom` 接口所有方法的 `Method` 对象**,**当我们调用某个方法时**,**相应方法的 `method`**、**代理对象 `$Proxy0` 实例还有方法参数一起传进了父类的 `h` 的 `invoke()` 方法中**,**所以我们在 `invoke()` 方法中就可以根据 `method` 通过反射调用真实对象的相应方法**,具体如下: ```java // 实现了 InvocationHandler 的动态代理类 public class DynamicProxy implements InvocationHandler { private Object mObject; // 真实对象引用 public DynamicProxy(final Object mObject) { this.mObject = mObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 通过反射调用真实对象的方法 Object result = method.invoke(mObject, args); return result; } } ``` 2. 我们回到 `Client` 的注释 1,所以**当我们调用 `Proxy` 类的 `newProxyInstance()` 方法时**,**这个方法在里面创建了代理对象**,并**返回代理对象 `$Proxy0` 实例**,所以**当我们调用代理对象的方法时**,我们**就是在调用 `$Proxy0` 相应的方法**,**这个方法处理逻辑就会委托给 `InvocationHandler` 实例的 `invoke()` 方法执行**(代理对象的父类持有 `InvocationHandler` 引用),`invoke()` **方法中就会通过反射调用我们真实对象的方法**(`InvocationHandler` 的实现类中持有真实对象的引用),**这就是动态代理的整个过程**。 ##### 3.2.1.5 原理 1. 动态代理的原理就是**通过反射机制动态生成代理类**,这是由于 **`JVM` 可以通过 `.class` 文件的二进制信息加载 `class` 对象的**。 2. 那么我们**在代码运行时**,**遵循 `.class` 文件的格式和结构**,**生成相应的二进制数据**,**然后再把这些二进制数据通过 JVM 加载成对应的 `class` 对象**。 3. 有了 `class` 对象,我们就可以**在运行时通过反射创建出代理对象的实例**,**这样就完成了在代码运行时**,**动态的创建一个代理对象的能力**,**这就是动态代理的原理**。 ##### 3.2.1.6 优缺点 ###### 3.2.1.6.1 优点 1. **代理类在程序运行时由反射自动生成**,**无需我们手动编写代理类代码**,**简化编程工作**。 2. 一个动态代理类 `InvocationHandler` 就**能代理多个被代理类**,**较为灵活**。 ###### 3.2.1.6.2 缺点 1. 动态代理**只能代理实现了接口的类**,而**不能代理实现抽象类的类**。 2. **通过反射调用被代理类的方法**,**效率低**。 ## 参考文献 1. [设计模式面试题(总结最全面的面试题!!!)](https://juejin.cn/post/6844904125721772039)。 2. [简说设计模式——代理模式](https://www.cnblogs.com/adamjwh/p/9102037.html)。 3. [设计模式(四)——搞懂什么是代理模式](https://zhuanlan.zhihu.com/p/72644638)。 4. [代理模式](https://juejin.cn/post/6844903544965857294)。 5. [设计模式之代理模式](https://www.justdojava.com/2019/09/28/java-proxy)。 6. [静态和动态代理模式](https://juejin.cn/post/6844903978342301709)。
ricear
2021年8月27日 15:23
©
BY-NC-ND(4.0)
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码