设计模式 - 静态代理 VS 动态代理
QA
动态代理是什么?有哪些应用?
动态代理是运行时动态生成代理类。
动态代理的应用有 spring aop、hibernate 数据查询、测试框架的后端 mock、rpc,Java注解对象获取等。
怎么实现动态代理?
JDK 原生动态代理和 cglib 动态代理。
- JDK 原生动态代理是基于接口实现的
-
!!!重要的两个类!!! Proxy类 Proxy.newProxyInstance 用来生成代理实例 InvocationHandler接口 要@Override invoke()方法,拦截所有原始类的各种方法,可在其前后增加逻辑
- 而 cglib 是基于继承当前类的子类实现的
-
!!!重要的两个类!!! Enhancer类 Enhancer.create(clazz, this) 获取被代理后的目标类 MethodInterceptor接口 要@Override intercept()方法,拦截所有原始对象的各种方法,可在其前后增加逻辑
1-为什么需要代理?
当想给一个类的每个method进行扩充(统计、打印耗时)、拦截(判断是否登录)时:
常规思路
- 修改了原来的代码逻辑
- 每个涉及的method都需要改动,改动量很大
代理思路
可以使用代理(静态代理/动态代理),统一处理这类额外功能(非核心业务功能),让原来的代码仍然只关心最核心的业务逻辑。
2-静态代理
关键点
代理类ServiceImplProxy和被代理类ServiceImpl实现同样的Service接口
代理类ServiceImplProxy持有被代理类ServiceImpl的引用
代理类ServiceImplProxy在接口方法中,填充扩展/拦截功能,核心逻辑依然由被代理类ServiceImpl的引用来完成
代码实例
接口 service
1 2 3 4 5 6 7 | public interface Service { void play(String xxx); String run(String xxx); } |
被代理类 ServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class ServiceImpl implements Service{ @Override public void play(String xxx){ //.... } @Override public String run(String xxx){ //... } } |
代理类 ServiceImplProxy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | public class ServiceImplProxy implements Service{ private Service service; //***关键*** 持有ServiceImpl的引用 public ServiceImplProxy() { service = new ServiceImpl(); } /** * 扩展功能 */ @Override public void play(String xxx){ long start = System.currentTimeMillis(); //计时开始 //***关键*** 执行真正的逻辑 service.play(xxx); long end = System.currentTimeMillis(); //计时结束 System.out.println( "耗时:" + (end - start) + "毫秒" ); //打印耗时 } /** * 拦截功能 */ @Override public String run(String xxx){ //添加拦截逻辑 if ( "xxx" .equals( "..." )){ //***关键*** 执行真正的逻辑 service.run(xxx); } //... } } |
优缺点
优点:
- 可以不修改原目标类(指ServiceImpl)的代码
- 可以在代理类中对功能进行拦截和扩充
缺点:
- 代理类需要实现与目标类一样的接口,会导致代理类数量较多,不易维护
- 一旦接口增加方法,目标类和代理类都需要维护
鉴于这两个缺点,JDK 提供了动态代理
3-动态代理,JDK(基于接口)
为何要引入JDK动态代理
为了解决静态代理的带来的以下问题 -> 引入了JDK动态代理:
- 代理类需要实现与目标类一样的接口,会导致代理类数量较多,不易维护
- 一旦接口增加方法,目标类和代理类都需要维护
代码示例
JDK动态代理的实现和静态代理一样,不同的是代理类的创建方式不同:
- 静态代理是直接新增一个代理类。
- JDK动态代理需要:
- 持有目标类对象 - 代理类的通过构造方法传入目标类对象(会),代理类持有目标类对象 (第1步掌握“目标类的实例”,与第2步自动生成代理类无关(因为Proxy.newProxyInstance只需传入clazz,不需要实例),与第3步反射调用方法有关(因为哪怕增加别的逻辑,但核心逻辑依然要调用“目标类的实例”)
- 创建代理对象 - 通过JDK的Proxy类, Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法获取代理对象, 和一个"调用处理器InvocationHandler"来实现的,通过Proxy来生成代理类实例,而这个代理实例通过调用处理器InvocationHandler的invoke()接收不同的参数, 反射调用真实对象的方法。
- 代理类实现 InvocationHandler 接口,重写invoke(Object proxy, Method method, Object[] args)方法。在此需要反射调用第1步传入的“目标类的实例”method.invoke(“目标类的实例”, args),并在此周围添加更多的逻辑,如过滤器等。
1-实现JDK动态代理的util代码(拷贝到任何项目中,都能直接使用)
!!!重要的两个类!!!
Proxy类
Proxy.newProxyInstance 用来生成代理实例
InvocationHandler接口
要@Override invoke()方法,拦截所有原始类的各种方法,可在其前后增加逻辑
2-实际例子
优缺点
优点:见“为何要引入JDK动态代理”
缺点:只能对该类,所实现接口中定义的方法,进行代理。
详细解析
- JDK 动态代理 https://blog.csdn.net/meism5/article/details/90744045
- JDK动态代理原理+为何只能代理接口,不能代理类 https://blog.csdn.net/qq30211478/article/details/77862121
4-动态代理,CGlib(基于类)
为何要引入CGlib动态代理
为了解决 JDK 的动态代理无法代理不实现接口的类的问题 -> 引入使用 CGLib 的实现动态代理。
CGLib(Code Generator Library)是一个强大的、高性能的代码生成库。底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。动态生成一个目标类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
关键点
- CGlib动态代理需要:
- maven中添加cglib依赖
- 不再持有目标类对象 - 构造函数就是默认的空构造函数
- 创建代理对象 - 使用cglib自带的Enhancer.create(clazz), 参数是目标类的clazz对象。Enhancer类是CGLib中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展,以后会经常看到它。
- 定义拦截器/处理器 - 在调用目标方法时,CGLib会回调MethodInterceptor(Object obj, Method method, Object[] params, MethodProxy proxy)接口方法拦截,来实现你自己的代理逻辑,类似于JDK中的InvocationHandler接口
- 参数:Object为由CGLib动态生成的代理类实例,Method为上文中实体类所调用的被代理的方法引用,Object[]为参数值列表,MethodProxy为生成的代理类对方法的代理引用。
- 返回:从代理实例的方法调用返回的值。
- 其中,proxy.invokeSuper(obj,arg) 调用代理类实例上的proxy方法的父类方法(即实体类TargetObject中对应的方法)
优缺点
优点:完全不受代理类必须实现接口的限制
缺点:对于final方法,无法进行代理
详细解析
CGLib 动态代理 https://blog.csdn.net/meism5/article/details/90781518
CGLIB(Code Generation Library) 介绍与原理 https://www.runoob.com/w3cnote/cglibcode-generation-library-intro.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?