设计模式【3.2】-- JDK动态代理源码分析有多香?
前面文章有说到代理模式:http://aphysia.cn/archives/dynamicagentdesignpattern
那么回顾一下,代理模式怎么来的?假设有个需求:
在系统中所有的
controller
类调用方法之前以及之后,打印一下日志。
假设原来的代码:
如果在所有的类里面都添加打印方法,这样肯定是不现实的,如果我有几百个这样的类,写到崩溃,况且重复代码太多,冗余,还耦合到一块了,要是我下次不打日志了,做其他的,那几百个类又全部改一遍。
静态代理
怎么样写比较优美呢?静态代理 这时候出场了,先把方法抽象成为接口:
让具体的类去实现 IProxy
,写自己的业务逻辑,比如:
然后弄个代理类,对方法进行增强:
调用的时候,把真实的对象放到代理类的构造器里面,就可以得到一个代理类,对它的方法进行增强,好处就是,如果下次我要改,不打日志,做其他事情,我改代理类就可以了,不用到处改我的目标类的方法,而坏处还是很明显,要增强哪一个类,就要为它写一个代理类,这样好像不是很合理。
动态代理
怎么样能让他自动生成代理对象呢? 动态代理做的就是这个事情,它可以 动态 的根据我们提供的类,生成代理类的对象。
最主要的,是在运行时,动态生成,只要传入我们要代理增强的类相关的信息,比如类对象本身,类加载器,类的接口等,就可以生成,不用提前知道它是 A 类,B 类还是 C 类。
动态代理主要有三种实现方法,今天我们重点分析 JDK 动态代理:
- JDK 代理:使用 JDK 提供的官方的 Proxy
- 第三方 CGlib 代理:使用 CGLib 的 Enhancer 类创建代理对象
- javassit:Javassist 是一个开源的分析、编辑和创建 Java 字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。
JDK 动态代理
使用步骤
- 新建一个接口
- 新建一个类,实现该接口
- 创建代理类,实现
java.lang.reflect.InvocationHandler
接口
代码如下:
IPlayDao.java
(玩的接口)
StudentDao.java
(实现了买东西,玩的接口的学生类)
MyProxy.java 代理类:
测试类(Test.java)
源码分析
跟着源码一步步看,先从调用的地方 Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
:
进入方法里面,省略各种异常处理,主要剩下了生成代理类字节码 以及 通过构造函数反射构造新对象:
上面注释里面说查找或者生成代理对象,为什么有查找?因为并不是每一次都生成,生成的代理对象实际上会缓存起来,如果没有,才会生成,看源码 Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces)
:
如果由实现给定接口的给定加载器定义的代理类存在,这将简单地返回缓存的副本; 否则,它将通过 ProxyClassFactory 创建代理类,proxyClassCache
其实就是个 weakCache
:
初始化的时候,proxyClassCache 指定了两个属性,一个是 KeyFactory
, 另外一个是 ProxyClassFactory
, 从名字就是猜到 ProxyClassFactory
是代理类工厂:
记住这里的 subKeyFactory,实际上就是传入的 ProxyClassFactory,那前面 proxyClassCache.get(loader, interfaces);
到底是怎么操作的?
上面调用到了 subKeyFactory.apply(key, parameter)
,这个 subKeyFactory
实际上是我们传的 ProxyClassFactory
, 进入里面去看:
上面调用一个方法生成代理类,我们看看 IDEA 反编译的代码:
生成代理文件实际上和我们想的差不多,就是一些 hashCode(),toString(),equals(), 原方法,代理方法等:
这与我们之前看到的文件一致:
然后之所以我们代码要设置写入磁盘,是因为这个变量, 控制了写磁盘操作:
为什么只支持接口实现,不支持继承普通类?
因为代理类继承了 Proxy 类,并且实现了接口,Java 不允许多继承,所以不能代理普通类的方式,并且在静态代码块里面,用反射方式获取了所有的代理方法。
JDK 代理看起来像是个黑盒,实际上,每一句代码,都有其缘由。其实本质上也是动态的为我们的原始类,动态生成代理类。
生成的代理类里面其实对原始类进行增强(比如 play()
方法)的时候, 调用了 super.h.invok()
方法,其实这里的 h
是什么呢?
h
是父类的 h
, 生成的代理类的父类是 Proxy
,Proxy 的 h
,就是我们传入的 InvocationHandler
:
生成的代码里面通过反射调用到的其实是我们自己重写的那部分逻辑,所以就有了增强的功能,不得不说,这种设计确实巧妙:
动态代理有多香
动态代理是Java语言里面的一个很强大的特性,可以用来做一些切面,比如拦截器,登录验证等等,但是它并不是独立存在的,任何一个知识点都不能独立说明语言的强大,重要的是它们的组合。
动态代理要实现强大的功能,一般需要和反射,注解等一起合作,比如对某些请求进行拦截,拦截后做一些登录验证,或者日志功能。最重要的一点,它能够在减少耦合度的前提下实现增强。
【作者简介】:
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。个人写作方向:Java源码解析
,JDBC
,Mybatis
,Spring
,redis
,分布式
,剑指Offer
,LeetCode
等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都完全正确,但是我保证所写的均经过实践或者查找资料。遗漏或者错误之处,还望指正。
__EOF__

本文链接:https://www.cnblogs.com/Damaer/p/15518467.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库