代理模式的原理解析
代理模式(Proxy Design Pattern)在不改变原始类(或叫被代理类)代码的情况下,通过引入代理类来给原始类附加功能。使用代理模式创建代理对象,让代理对象控制原始对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象等等。
- Subject 为 RealSubject 和 Proxy 提供了接口,通过实现同一接口,Proxy 可以在 RealSubject 出现的地方取代它。
- RealSubject 是真正做事的对象,它是被 Proxy 代理和控制访问的对象。
- Proxy 持有 RealSubject 的引用,客户与 RealSubject 的交互都必须通过 Proxy 来控制。而在某些情况下,我们可能需要这样的控制。
在某些情况下,如果原始类并没有定义接口,并且原始类代码也并不是我们开发维护的(比如它来自一个第三方的类库),我们没办法直接修改原始类,给它重新定义一个接口。那我们该如何实现代理模式呢?对于这种外部类的扩展,我们一般都是采用继承的方式。这里也不例外。我们可以让代理类继承原始类,然后扩展附加功能。
代理模式的应用
1. 远程代理
远程代理可以作为另一个 JVM 进程上的对象的本地代表。通过代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再由代理将结果转给客户。
远程代理的典型应用场景就是 RPC。通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用 RPC 服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC 服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。
2. 虚拟代理
虚拟代理作为创建开销大的对象的代表。虚拟代理经常直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建后,代理就会将请求直接委托给对象。典型场景如 MyBatis 的一对多查询,查询结果会先返回一个虚拟代理对象,等真正需要获取多条数据时才会去查询数据库。
3. 动态代理
所谓动态代理(Dynamic Proxy),就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类、动态处理代理方法调用,然后在系统中用代理类替换掉原始类。很多场景都是利用类似机制做到的,比如用来包装 RPC 调用、面向切面的编程等等。
那如何实现动态代理呢?
其实实现方式有很多:比如 JDK 自身已经提供的动态代理的语法(实际上,JDK 动态代理底层依赖的就是 Java 的反射语法)。还可以利用更高性能的字节码操作机制,类似 ASM、cglib、Javassist 等。JDK 动态代理使用示例如下所示:
- Proxy 用于创建代理对象
- InvocationHandler 实现了代理的行为
实际上,Spring AOP 底层的实现原理就是基于动态代理。用户配置好需要给哪些类创建代理,并定义好在执行原始类的业务代码前后执行哪些附加功能。Spring 为这些类创建动态代理对象,并在 IOC 容器中替代原始类对象。原本在代码中执行的原始类的方法,被换作执行代理类的方法,也就实现了给原始类添加附加功能的目的。
FAQ
代理模式和装饰者模式的区别?
有时候这两者的确看起来很像,但是它们的目的是不一样的。装饰者模式是为对象增加行为;而代理模式是控制对象的访问。
代理模式和适配器模式的区别?
代理和适配器都是挡在其他对象的前面,并负责将请求转发给它们。适配器会改变对象适配的接口,而代理则实现相同的接口。
如何让客户使用代理对象,而不是真正的对象?
一个常用的技巧是提供一个工厂类,用来实例化并返回对象实例。因为这是在工厂方法内发生的,我们可以用代理包装原始对象实例后再返回,而客户不知道也不在乎他使用的是代理对象还是真实对象。