标签
代理模式在控制对象访问和扩展对象功能方面非常有用,适用于对目标对象访问进行控制的场景。在实现代理模式时,可以根据需求选择不同的代理方式,灵活地为系统增加权限、日志、性能监控等功能。不过,代理模式增加了一层间接性,需要在性能和可维护性之间进行权衡。
定义
代理模式(静态代理)是一种结构型设计模式,它通过代理对象来控制对目标对象的访问。代理对象提供了与目标对象相同的接口或方法,使得客户端可以间接地访问目标对象,同时能够在访问目标对象的过程中进行控制或增强,例如添加访问权限控制、延迟加载、日志记录等功能。
结构
代理模式主要包含以下角色:
- 抽象接口(Subject):定义目标对象和代理对象的公共接口,客户端通过该接口访问目标对象。
- 真实对象(RealSubject):实际处理请求的对象。
- 代理对象(Proxy):持有对真实对象的引用,控制对真实对象的访问,并可以在请求前后加入额外的逻辑。
类图
代码实现
假设一个银行账户服务
BankAccount
,提供了 deposit
和 withdraw
方法。我们希望通过代理对象来控制访问,以进行日志记录和权限控制。1. 抽象接口
2. 真实对象
3. 代理对象
4. 客户端代码
注意事项
- 选择合适的代理类型:代理模式有多种实现类型,包括静态代理、动态代理、CGLIB代理等,选择时应根据需求决定使用哪一种实现。例如,动态代理可以在运行时动态生成代理类,非常适合 AOP 场景。
- 性能影响:代理模式会在每次方法调用时增加一层间接访问的开销。对于性能要求高的场景,要谨慎使用代理,以免影响系统性能。
- 代码可维护性:代理类通常与真实对象实现相同的接口,若接口发生变更,代理和真实对象都需要同步修改,可能增加维护成本。因此,在设计代理模式时,尽量保证接口的稳定性。
- 权限控制:在代理模式中,通常会引入权限控制或访问限制逻辑,确保用户或调用方只能访问其有权限的操作。在这种情况下,要注意权限配置的安全性和适用范围。
扩展
JDK 动态代理
JDK 提供了动态代理机制,通过
java.lang.reflect.Proxy
和 InvocationHandler
实现。动态代理可以在运行时生成代理类,避免手动编写静态代理类。它适用于代理实现了接口的目标对象。优势:
- 无需显式实现代理类,代码量较少。
- 灵活性高,可动态定义代理行为。
示例代码
静态代理与动态代理:
- 静态代理:编译时创建代理类,代理类在代码中显式实现接口,代码量较多且不灵活,适合简单代理需求。
- 动态代理:运行时生成代理类,Java 提供了
java.lang.reflect.Proxy
支持动态代理,适合在运行时需要代理类的场景。
CGLIB 代理:
CGLIB 代理是一种基于字节码的代理方式,可以代理没有实现接口的类,适合于需要代理类扩展的情况。Spring AOP 中就使用了 CGLIB 代理来实现对目标对象的增强。
应用场景:
- 远程代理:代理对象代表另一个远程对象,客户端可以像调用本地对象一样进行操作。
- 虚拟代理:代理对象在需要时才创建真实对象,常用于延迟加载资源密集的对象。
- 保护代理:通过代理控制对对象的访问权限,例如设置不同用户权限控制。
优势:
- 能够代理没有实现接口的目标类。
- 性能优于 JDK 动态代理,尤其是在代理方法较多时。
注意:
- 被代理的类不能是
final
,否则无法生成子类。
- Spring AOP 在目标对象未实现接口时,会使用 CGLIB。
示例代码
比较总结
特点 | JDK 动态代理 | CGLIB 动态代理 |
代理对象限制 | 仅支持实现了接口的类 | 支持未实现接口的类 |
实现方式 | 基于 Java 反射和接口实现 | 基于字节码生成子类 |
性能 | 方法较少时性能接近或优于 CGLIB | 方法较多时性能优于 JDK 动态代理 |
代理类限制 | 无法代理 final 方法 | 无法代理 final 类和 final 方法 |
Spring 使用场景 | 默认使用,目标类实现了接口 | 目标类未实现接口时,Spring 自动切换为 CGLIB |
在实际应用中:
- 若目标对象实现了接口,优先使用 JDK 动态代理。
- 若目标对象未实现接口,选择使用 CGLIB 动态代理。