【设计模式】代理模式-原理、实现以及应用场景

本文深入解析代理模式,比较JDK与CGLib动态代理,探讨静态代理的局限并展示动态代理的灵活性。涵盖了动态代理的原理、动态代理实现(包括JDK和CGLib)、两者对比、优缺点及实际应用场景,如业务开发和RPC缓存。

代理模式原理

代理模式(Proxyy)就是在不改变原始类(或叫做被代理类)代码的情况下,通过引入代理类来给原始类附加功能。

静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)一起实现相同的接口或者是继承相同父类应用实例。

优缺点

优点:
1. 在不修改目标对象的功能前提下,能通过代理对象对目标进行拓展
缺点:
1. 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,增加了不必要的开发成本。
2. 一旦接口增加方法,目标对象与代理对象都要维护。

动态代理

动态代理可以解决上述静态代理的缺点问题。
所谓动态代理,就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

实现方式

  • JDK实现:InvocationHandler接口
  • CGLib实现:MethodInterceptor接口

CGLIb要求目标代理类中不能有final关键字修饰的方法

JDK动态代理生成对象步骤

  1. 获取被代理对象的引用,并获取它的所有接口,通过反射获取。
  2. JDK动态代理类重新生成一个新类,同时新类实现被代理类实现的所有接口。
  3. 动态生成java代码,新加的业务逻辑方法由一定的代码逻辑调用,$Proxy0。
  4. 编译新生成的java代码.class文件。
  5. 重新加载到JVM中。

CGLib采用了FastClass机制

  1. 为代理类和被代理类各生成一个class。
  2. class会为代理类和被代理类方法分配一个index。
  3. idnex当做入参,FastClass就可以直接定位要调用的方法。

JDK和CGLib对比

实现方式

  • JDK实现了被代理的接口
  • CGLib继承了被代理的接口

生成字节码

  • JDK直接生成字节码
  • CGLib通过asm框架生成class字节码
  • CGlib生成代理类比JDK动态代理效率低

调用代理方法

  • JDK通过反射机制调用
  • CGLib通过FastClass机制直接调用
  • CGLib执行效率比JDK高

静态代理与动态代理区别

静态代理

  1. 只能通过手动完成代理操作,硬编码
  2. 被代理类新增了方法,代理类也要同步增加,违背了开闭原则

动态代理

  1. 采用运行时动态生成代码的方式,反射实现
  2. 对目标类的增加逻辑拓展,只需要新增策略类即可完成,无需修改代理类的代码

代理模式的优缺点

优点

  1. 能将代理对象与真实被调用目标对象分离
  2. 在一定程度上降低了系统的耦合度,拓展性好
  3. 起到保护目标对象的作用
  4. 增强目标对象的功能

缺点

  1. 造成了系统中类的数量增加
  2. 增加了代理对象,会导致请求处理速度变慢
  3. 增加了系统的复杂度

代理模式的应用场景

1. 业务系统的非功能性需求开发

代理模式最常用的一个应用场景就是,在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。实际上,前面举的搜集接口请求信息的例子,就是这个应用场景的一个典型例子。
如果你熟悉 Java 语言和 Spring 开发框架,这部分工作都是可以在 Spring AOP 切面中完成的。前面我们也提到,Spring AOP 底层的实现原理就是基于动态代理。

2. 代理模式在RPC、缓存中的应用

实际上,RPC框架也可以看做是一种代理模式,GoF的《设计模式》一书中把它称作远程代理。通过远程代理,将网络通信,数据编码等细节隐藏起来。客户端在使用RPC服务的时候,就像在使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注客户端的交互细节。

我们再来看代理模式在缓存中的应用。假设我们要开发一个接口请求的缓存功能,对于某些接口请求,如果入参相同,在设定的过期时间内,直接返回缓存结果,而不用重新进行逻辑处理。比如,针对获取用户个人信息的需求,我们可以开发两个接口,一个支持缓存,一个支持实时查询。对于需要实时数据的需求,我们让其调用实时查询接口,对于不需要实时数据的需求,我们让其调用支持缓存的接口。那如何来实现接口请求的缓存功能呢?最简单的实现方法就是刚刚我们讲到的,给每个需要支持缓存的查询需求都开发两个不同的接口,一个支持缓存,一个支持实时查询。但是,这样做显然增加了开发成本,而且会让代码看起来非常臃肿(接口个数成倍增加),也不方便缓存接口的集中管理(增加、删除缓存接口)、集中配置(比如配置每个接口缓存过期时间)。

针对这些问题,代理模式就能派上用场了,确切地说,应该是动态代理。如果是基于 Spring 框架来开发的话,那就可以在 AOP 切面中完成接口缓存的功能。在应用启动的时候,我们从配置文件中加载需要支持缓存的接口,以及相应的缓存策略(比如过期时间)等。当请求到来的时候,我们在 AOP 切面中拦截请求,如果请求中带有支持缓存的字段(比如 http://…?..&cached=true),我们便从缓存(内存缓存或者 Redis 缓存等)中获取数据直接返回。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值