前言:本文为《Java帝国之动态代理》的姊妹篇, 讲述动态代理的另外一种实现即CGLib的设计过程。
当IO大臣绞尽脑汁地在府中设计Java动态代理的时候,他并不知道,在帝国的一个小小的部落,一个年轻的小伙子正在为同样的问题而苦恼。
师傅刚刚给小伙子下达了任务:在运行时对一个类进行扩展, 例如有个类叫HelloWorld,要在运行时给他加点日志输出的代码。
师傅特别告诉年轻人:“大胖, 你要注意,是在运行时,而不是在编译时! 不能改变原来的代码!”
年轻气盛的张大胖还不太清楚为什么要这么干,不由地问道:“师傅,怎么会有这么稀奇古怪的要求啊?”
师傅说:“你先把这个东西给做出来,我以后自然会给你讲。”
张大胖研究了一段时间后得知,Java帝国严禁在运行时对一个类进行修改,比如HelloWorld 这个类, 一旦被装载进入JVM方法区,就不能改动了,那想在运行时给它的sayHello()方法中加点日志代码该怎么办呢?
大胖心想: 那其实只剩下一条路了,动态地生成一个新类, 让这个类作为代理HelloWorld去做事情。
可是怎么才能动态地生成一个类呢? 张大胖心想: 我曾经看过帝国的《JVM规范》, 一个class文件那简直是太复杂了,难道让我去操作二进制代码去生成类? 那可要了我的小命了!
张大胖拿着草稿图去找师傅:“师傅,我已经有了个初步想法, 可是不知道怎么去动态地生成类啊!”
师傅看了一眼说: “你这个类图有问题啊!”
“什么问题?”
“举个例子,有个函数如下
现在你把HelloWorld变成了HelloWorldProxy, 而这个HelloWorldProxy又和原来的HelloWorld一毛钱关系都没有, 还能作为参数传递給hello方法吗? ”
张大胖挠挠头,挺不好意思 : “奥,那是肯定不行喽,没法利用多态,这样,我可以让这个动态生成的HelloWorldProxy继承自HelloWorld, 师傅你看看这样行不行:”
“这样好多了, 但是还有一个问题,就是代码的复用性不够。”
“什么复用性?”
“你想想啊,现在你只有一个类就是HelloWorld , 如果还有很多别的类例如Person, Student, Employee,Teacher ..... 他们相应的方法都要加上日志输出,按照你的办法,就得有无数这样的代码了:
Logger.startLog();
super.XXXX();
Logger.endLog();”
“奥,我明白了,就是要想办法复用这一段代码,那我可以再增加一个中间层,就叫做LogInterceptor如何?”
“孺子可教,你这个名称起得也不错,Interceptor,意味着拦截的意思。这样一来LogInterceptor就可以被其他类给复用了。”
“师傅, 回到我最初的问题, 怎么在运行时动态地生成Java Class啊? 总不能让我直接写Java字节码吧?”
“这个你不用担心,有个叫做ASM的家伙,他已经对底层的Java字节码操作做了封装,你直接调用它就行了”
(老刘提示: 请移步 《ASM: 一个低调成功者的自述》)
“好, 让我去看看,这个玩意儿到底怎么样。”
ASM确实挺难的, 虽然对字节码操作做了封装,但是非得理解JVM指令才行,张大胖不得不去学习一下JVM字节码的知识,两个月后,他终于能够使用ASM动态的在内存中创建类了。
又花了两个月,张大胖终于把整个系统开发完成,现在的使用非常简单:
张大胖把这个东西命名为动态代理, 因为所做的所有事情无非就是在运行时为原有的类建立一个代理,增加功能而已。
过了两天, 师傅急匆匆地来找张大胖:“大胖, 我刚刚听说, Java帝国的IO大臣在JDK中加入了一个重要功能,叫做Java 动态代理,你赶紧研究下,看看和咱们做的有什么不同。”
大胖不敢怠慢,赶紧查看帝国发布的公告文书, 看完以后就放心了:“师傅, 这官方的动态代理有个重大的缺陷,就是必须有接口才能使用,而我们做的动态代理只要有个类就可以了, 我可以动态地生成一个子类。当然如果一个类被标记为final , 无法被继承,那就不行了。”
“嗯,有点意思” 师傅说道 “这官方动态生成的HelloWorldProxy是HelloWorld的兄弟, 而我们动态生成的HelloWorldProxy是HelloWorld的孩子啊!”
“哈哈,果然是这样。 师傅,你还没给我说这玩意儿到底有啥用处呢”
“你没看官方的公告吗?”
“官方大话套话连篇,看不懂啊!”
“还拿之前的例子来说吧,你现在有很多类,例如Person, Student, Employee,Teacher ... , 每个类都有很多方法, 现在你想给这些方法加上日志输出,该怎么办呢?”
张大胖说:“我可以去改动代码, 嗯,这样改动量非常大,并且如果拿不到源码的话,就没办法了。”
“对啊,这时候动态代理不就派上用场了? 动态生成代理类PersonProxy, StudentProxy,EmployProxy... 等等, 让它们去继承Person, Student, Employee, 这样代理类就可以增加日志输出代码了。 你甚至可以把要添加日志功能的类和方法写到一个XML文件中去, 然后再写个工具去读取这个XML文件,自动地生成所有代理类,多方便啊。”
“奥,原来如此 ,不仅仅是日志,还有事务了, 权限检查了,都可以用这种办法,对吧 ” 张大胖一点就通。
“是这样的, 这就是AOP编程了。 对了,既然官方已经把动态代理这个名称给占了, 我们就得改名了,不能叫做动态代理了”
“师傅,这个我已经想好了,叫做Code Generation Library, 简称CGLib, 体现了技术的本质,就是一个代码生成的工具。”
后记:CGLIb为了提高性能,还用了一种叫做FastClass的方式来直接调用一个对象的方法,而不是通过反射。 由于涉及的代码太多,本文不再展示,一个具体的调用过程参见下图:
最后,感谢“白色衬衫”同学在之前的留言中提出了“动态代理的兄弟、父子的关系”,给了我灵感写这篇文章。