从字节码探索代理模式
小伙伴们好呀~ 😝 ,4ye 今天来和小伙伴们分享下以下的小知识点~
静态代理
JDK 动态代理
反编译动态字节码
静态代理
如图所示,除了维护正常的一个实现类外(被代理类),还需要而外维护一个代理类,而且这些都是需要硬编码的,感觉挺麻烦的~
那么咱们先来快速感受下这个小例子👇
如上图所示
定义一个 接口,
代理类和被代理类都去实现该接口,同时代理类要聚合被代理类,
在客户端中调用这个代理类的方法,便完成了这个静态代理。
结果如下~
这里利用 IDEA
生成了这个 UML
图,也挺直观的~
那么,快速感受下这个小例子后,你是否也觉得挺麻烦的呢?
比如:
如果接口新增方法,那么就得维护多一个类 🐷
接口越多,代理类越多 😵
所以为了解决这些缺点,咱们就需要来看看这个 JDK
的动态代理了~ 😄
果然,懒惰让世界变得更美好~ 哈哈(谁也不想多写些✍)
JDK 动态代理
如图所示~
JDK
动态代理的精髓就在这两个步骤
实现
InvocationHandler
接口,重写invoke
方法通过
Proxy.newProxyInstance(classLoader,interfaces,invocationHandler)
方法获取代理类
InvocationHandler
Proxy.newProxyInstance
没有,就上面两步了😝
结果如下图~
注意点
这里还要注意一点,Proxy.newProxyInstance
中的第二个参数,接口! 要用接口的实现类去获取接口(不能直接写通过 接口.class.getInterfaces()
,不然会抛出下面这个异常~ 🐖
动态代理异常 com.sun.proxy.$Proxy0 cannot be cast to
这里再多做一层封装即可~,就不用担心写错了😄
优化后如下👇
不过这样会代理实现类的所有接口,如果需要特殊定制的话,也可以将第二接口参数替换为 new Class[]{ISayService.class},
即可。
本来还想继续探索下,看看底层到底怎么实现这个动态代理,结果看到又是调用 native
方法, 想了下还是算了先 哈哈哈 不然都更不出文章了😄
不过源码注释还是很重要的!😝 记录下这个 InvocationHandler
接口源码~
可以发现这最后一句介绍很有意思~
当在这个代理类上调用方法时,会将调用的方法编码后发到 InvocationHandler
实现类中进行处理
反编译
本以为到这就愉快的进入下一个小结~,结果突然想到 反编译动态代理的 class
文件 ,于是又开始折腾了 哈哈😝
arthas
于是乎,我下了个 arthas
,在 window
上运行有点忐忑……
结果它真的一直运行不起来,没有报错,就一直卡在那里,没有出现 arthas
那个五颜六色的标志…… 让我一度怀疑我电脑坏了
先看下步骤吧~
demo
说明,如图,只是一个简单的 main
方法
打个断点
通过
jps
查看这个进程ID
根据操作手册说明,window
上直接运行 as.bat 54792
命令就行了 这个 54792 就是这个 java
程序的 pid
,结果画面就变成这样了,一动不动的……
我也跟着瞎琢磨了大半天,后面想着会不会是因为我打了断点的原因,但是不打断点就结束了呀~
于是我尝试着将代码放到之前的 Springboot WEB
项目中,再次运行起来,结果真的可以了~
(中间还把内存搞崩了 哈哈哈,连 arthas
都启动不起来,提示我 pagesize
太小)🙃
开心滴打开浏览器 ~
哇瑟,终于正常了 🐷
我迫不及待的运行了这个 反编译代码语句: jad com.sum.proxy.$Proxy0
看看效果,结果
看到下面这一幕,有种恍然大悟的感觉~
难怪以前 debug
看到 JDK 动态代理 时,都会出现一个 h
变量!😝
但是呢,我还没有看到 我自己写的动态代理类,主要是忘打断点查看~ 无奈 我又想着用别的办法折腾下,总感觉这么写太浪费了~ 为了测试这个 jdk 动态代理 ,居然得把代码拷贝到 web
项目中运行,实在太扯了。
于是又开始折腾了 哈哈
HSDB
搜素一番后,我又找到一个神器! jdk
自带的 HSDB
全名 HotSpot Debugger
😝
这个使用也比较简单,
启动命令:
java -classpath "%JAVA_HOME%/lib/sa-jdi.jar" sun.jvm.hotspot.HSDB
通过
pid
找到对应的 java 进程点击 Tools 》class Browser 就可以打开下面这个窗口
输入
$Proxy
就可以查找了点击目标类,然后点击 Create .class File 就会在你使用 该命令的目录下 创建这个字节码文件了
将该 class
文件 拷贝到 IDEA 中,打开即可
从这份代码中可以看到,该代理类不仅仅代理了 ISayService
这个接口中的 say
方法,连 Object
的 toString
, hashCode
,equals
这三个方法也都被代理了!
于是我尝试着运行下,果然没有骗我~ 哈哈哈😄
那么为啥要代理
Object
的这三个方法呢?
发现我们平时最多也就重写这三个方法了,其他方法基本都被 final
修饰,不能被重写,剩下可以被重写的方法就finalize
和 clone
了~ (更没用过了 😅)
ProxyGenerator
这个是最简单的方法了,直接将 JDK 动态代理 生成的字节码文件保存到工程目录下~
在 IDEA
的启动配置参数(VM options
)中,加入下面的描述~
jdk8
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
jdk8 之后的版本
-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true
如图所示~
运行后可以发现工程中多了下面这份字节码文件!
居然还有这操作!原理也简单的,小伙伴们在 IDEA
中双击 shift
键,搜索 ProxyGenerator
类就可以啦
总结
由于篇幅的原因,下文再来说说这个 Cglib
动态代理~ ,现在先来小结下~😝
一.静态代理
【优点】👉:
容易理解
编写简单
【缺点】👉:
随着接口方法的增加,或者接口的增加,需要同时维护代理类和被代理类,成本高
二.JDK 动态代理:
【优点】👉:
不用创建和维护代理类
【缺点】👉:
只有实现了接口的类才能被代理,有局限性
三.反编译
可以直接使用 JDK
自带的 HSDB
来完成反编译 ( HotSpot Debugger
),也可以通过这个arthas
,或者通过 ProxyGenerator
类的 saveGeneratedFiles
属性来将生成的字节码保存到工程文件中,就可以快速地看到这个动态代理的字节码文件了
四.本文小结图
这里 4ye 重新整理了下上面相关的知识点,分成下面两张图,小伙伴们如果需要可以直接在公众号后台回复 "代理模式 1",或者微信我就行了~ 😝
代理图:
反编译图:
最后
欢迎小伙伴们来一起探讨问题~
如果你觉得本篇文章还不错的话,那拜托再点点赞支持一下呀😝
让我们开始这一场意外的相遇吧!~
欢迎留言!谢谢支持!ヾ(≧▽≦*)o 冲冲冲!!
我是 4ye 咱们下期应该……很快再见!! 😆
如果文章对您有所帮助,欢迎关注公众号 J a v a 4 y e 😆
版权声明: 本文为 InfoQ 作者【4ye】的原创文章。
原文链接:【http://xie.infoq.cn/article/9a88ed4721c9c15c8b7bac6e7】。文章转载请联系作者。
评论