在上一篇中阔加,我們還留下一個很復(fù)雜的問題,即如何創(chuàng)建一個final
類的代理满钟。
對于一般的類來說胜榔,我們使用cglib來創(chuàng)建代理的時候,只需要調(diào)用setSuperClass
便足夠了湃番。然而夭织,如果將一個final
類作為參數(shù)傳遞過去,那么會拋出一個IllegalArgumentException: Cannot subclass final class
吠撮。
要解決這個問題尊惰,途徑并不多。final
作為一個非常關(guān)鍵的關(guān)鍵字,Java和Java虛擬機(jī)會在編譯器和運(yùn)行期都進(jìn)行檢查弄屡。以確保并不會有某個類是一個final
類的子類题禀。
好在,我們可以使用一些小伎倆琢岩。要知道的一點(diǎn)是投剥,JVM只有在加載了類之后,才會知道這是一個final
類担孔。所以江锨,我們要做的就是,在類完成加載之前糕篇,將一個final
類修改為非final
類啄育。
沒錯,這就是要用到自定義類加載技術(shù)了拌消。
ClassLoader
一般而言挑豌,如果要實(shí)現(xiàn)自定義的ClassLoader
,只需要繼承ClassLoader
這個抽象類墩崩,而后重寫findClass
方法就可以氓英。并且在findClass
里面調(diào)用defineClass
方法。
class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// todo
// byte[] bytes = new byte[];
return defineClass(name, bytes, 0, bytes.length);
}
}
實(shí)現(xiàn)ClassLoader并不難鹦筹,難的是如何修改铝阐。
ASM
在Java里面,有一類工具叫做字節(jié)碼操作工具铐拐。這一類工具常見于復(fù)雜框架中徘键。
使用這一類工具一般有兩個理由:
- 一些信息只有在運(yùn)行期才能拿到,例如生成各種代理的框架遍蟋;
- 另外一個理由就是吹害,表達(dá)一些Java語言無法表達(dá)的語義。常見于各種基于JVM語言的編譯工具里面虚青;
這一次我們使用ASM工具它呀。ASM和其余的諸如Javaassis, BCEL比起來,具有抽象層次高挟憔,功能完備的特點(diǎn)钟些,美中不足的是,使用這個ASM工具要求對Java的字節(jié)碼指令和.class
文件格式有較深的理解绊谭。cglib也是一種字節(jié)碼操作工具政恍,但它是在ASM基礎(chǔ)上的更高一級的抽象。
這一次我們只使用ASM里面的event based API
达传。event based API
的核心是實(shí)現(xiàn)一個類篙耗,并且繼承ClassVisitor
迫筑。
抽象類ClassVisitor
其實(shí)并沒有抽象方法。它里面的實(shí)現(xiàn)宗弯,就是按照讀取的字節(jié)碼流脯燃,原樣返回。ClassVisitor
是依照Visitor
模式設(shè)計(jì)的蒙保,所以我們可以實(shí)現(xiàn)多個ClassVisitor
以完成不同的功能辕棚。
這一次我們只需要實(shí)現(xiàn)一個就夠了。這個實(shí)現(xiàn)的ClassVisitor
非常簡單邓厕,在讀取.class文件的時候逝嚎,將類的final
標(biāo)記去掉。
因此我們只需要重寫:
這個方法有一大堆參數(shù)详恼,不過我們只需要關(guān)注access
參數(shù)补君。因?yàn)檫@個參數(shù)代表的就是一個類的“訪問性”。通過閱讀文檔我們能知道昧互,final
是由16
所代表的挽铁,二進(jìn)制的話則是10000
。常量org.objectweb.asm.Opcodes#ACC_FINAL
恰好代表這個敞掘。
所以我們重寫的地方很簡單:
public class RemoveFinalFlagClassVisitor extends ClassVisitor {
public RemoveFinalFlagClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM7, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
// we have the final flag
if((access & Opcodes.ACC_FINAL) == Opcodes.ACC_FINAL) {
//remove the final flag
access = access ^ Opcodes.ACC_FINAL;
}
super.visit(version, access, name, signature, superName, interfaces);
}
}
代碼實(shí)際上很簡單叽掘,只不過是用位運(yùn)算了實(shí)現(xiàn)而已。
在調(diào)用super.visit
的時候玖雁,我們就已經(jīng)把final
關(guān)鍵字去掉了够掠。
ClassLoader的實(shí)現(xiàn)
這段代碼也不難。ClassWriter
自身也繼承了ClassVisitor
茄菊,所以實(shí)際上它和我們寫的RemoveFinalFlagClassVisitor
構(gòu)成了一個Visitor
鏈。
另外一個地方就是構(gòu)造函數(shù)里面赊堪,我調(diào)用的是super(null)
面殖,這只是為了將類加載器的parent
設(shè)置為null
,從而避免在loadClass
的類先被父加載器加載了——如果被父加載器加載哭廉,我們就無法去掉final
了脊僚,除非這個類不在父加載器的classpath
里面。
換句話來說遵绰,這種實(shí)現(xiàn)破壞了著名的雙親委托模型辽幌。
自己做的孽,含著淚也要頂住
于是我們會陷入另外一個問題椿访,在雙親委托模型被破壞之后乌企,我們的代碼會出現(xiàn)一個很微妙的問題:
這個單測居然失敗了,驚不驚喜成玫,意不意外加酵?拳喻?
而且失敗原因不是assertNotNull
斷言失敗,而是第20行失敗了猪腕,異常是java.lang.ClassCastException
!詳細(xì)的信息是:
java.lang.ClassCastException: class cn.com.flycash.stupidmock.testobj.FinalObject cannot be cast to class cn.com.flycash.stupidmock.testobj.FinalObject (cn.com.flycash.stupidmock.testobj.FinalObject is in unnamed module of loader cn.com.flycash.stupidmock.classloader.StupidMockClassLoader @50de0926; cn.com.flycash.stupidmock.testobj.FinalObject is in unnamed module of loader 'app')
也就是冗澈,在類型轉(zhuǎn)換的時候,虛擬機(jī)發(fā)現(xiàn)創(chuàng)建實(shí)例的類的加載器是我們自定義的StupidMockClassLoader
陋葡,而要轉(zhuǎn)換到的類型雖然也是FinalObject
亚亲,然而卻是使用AppClassLoader
加載的。
我們可以在AppClassLoader#loadClass
里面打上斷點(diǎn)腐缤,可以清楚看到捌归,在我們的加載器加載了一次FinalObject.class
之后,AppClassLoader
又加載了一次柴梆。
出現(xiàn)這個問題根源在于陨溅,從Java語言層面上來說,或者編譯層面上來說绍在,類的全限定名就代表了類的唯一性门扇,而在虛擬機(jī)層面上,類加載器+類全限定名才是類的唯一性偿渡。
這種割裂臼寄,在開發(fā)各種框架的時候,會坑死人溜宽。
直接原因再說我在使用IDE運(yùn)行測試的時候吉拳,FinalObject
編譯后的文件FinalObject.class
出現(xiàn)在了AppClassLoader
的classpath
之下。
那么現(xiàn)在有兩個問題:
- 為什么JVM會觸發(fā)第二次加載适揉?畢竟按照理論上來說留攒,我們自定義的類加載器已經(jīng)加載到了這個類!
- 怎么解決這個問題嫉嘀?
這一章要解決的問題是“如何創(chuàng)建final類的代理”炼邀,到這里也算是創(chuàng)建出來了,只是又引出來新的問題剪侮。
新的問題且隨它去拭宁,下一篇我將解釋其中的一個問題。