這篇文章將為大家詳細(xì)講解有關(guān)JDK動(dòng)態(tài)代理之ProxyGenerator如何生成代理類的字節(jié)碼文件,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
站在用戶的角度思考問題,與客戶深入溝通,找到烏當(dāng)網(wǎng)站設(shè)計(jì)與烏當(dāng)網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:成都網(wǎng)站制作、成都做網(wǎng)站、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、域名注冊(cè)、網(wǎng)絡(luò)空間、企業(yè)郵箱。業(yè)務(wù)覆蓋烏當(dāng)?shù)貐^(qū)。
通過(guò)前面幾篇的分析,我們知道代理類是通過(guò)Proxy類的ProxyClassFactory工廠生成的,這個(gè)工廠類會(huì)去調(diào)用ProxyGenerator類的generateProxyClass()方法來(lái)生成代理類的字節(jié)碼。ProxyGenerator這個(gè)類存放在sun.misc包下,我們可以通過(guò)OpenJDK源碼來(lái)找到這個(gè)類,該類的generateProxyClass()靜態(tài)方法的核心內(nèi)容就是去調(diào)用generateClassFile()實(shí)例方法來(lái)生成Class文件。我們直接來(lái)看generateClassFile()這個(gè)方法內(nèi)部做了些什么。
private byte[] generateClassFile() { //第一步, 將所有的方法組裝成ProxyMethod對(duì)象 //首先為代理類生成toString, hashCode, equals等代理方法 addProxyMethod(hashCodeMethod, Object.class); addProxyMethod(equalsMethod, Object.class); addProxyMethod(toStringMethod, Object.class); //遍歷每一個(gè)接口的每一個(gè)方法, 并且為其生成ProxyMethod對(duì)象 for (int i = 0; i < interfaces.length; i++) { Method[] methods = interfaces[i].getMethods(); for (int j = 0; j < methods.length; j++) { addProxyMethod(methods[j], interfaces[i]); } } //對(duì)于具有相同簽名的代理方法, 檢驗(yàn)方法的返回值是否兼容 for (Listsigmethods : proxyMethods.values()) { checkReturnTypes(sigmethods); } //第二步, 組裝要生成的class文件的所有的字段信息和方法信息 try { //添加構(gòu)造器方法 methods.add(generateConstructor()); //遍歷緩存中的代理方法 for (List sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { //添加代理類的靜態(tài)字段, 例如:private static Method m1; fields.add(new FieldInfo(pm.methodFieldName, "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC)); //添加代理類的代理方法 methods.add(pm.generateMethod()); } } //添加代理類的靜態(tài)字段初始化方法 methods.add(generateStaticInitializer()); } catch (IOException e) { throw new InternalError("unexpected I/O Exception"); } //驗(yàn)證方法和字段集合不能大于65535 if (methods.size() > 65535) { throw new IllegalArgumentException("method limit exceeded"); } if (fields.size() > 65535) { throw new IllegalArgumentException("field limit exceeded"); } //第三步, 寫入最終的class文件 //驗(yàn)證常量池中存在代理類的全限定名 cp.getClass(dotToSlash(className)); //驗(yàn)證常量池中存在代理類父類的全限定名, 父類名為:"java/lang/reflect/Proxy" cp.getClass(superclassName); //驗(yàn)證常量池存在代理類接口的全限定名 for (int i = 0; i < interfaces.length; i++) { cp.getClass(dotToSlash(interfaces[i].getName())); } //接下來(lái)要開始寫入文件了,設(shè)置常量池只讀 cp.setReadOnly(); ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); try { //1.寫入魔數(shù) dout.writeInt(0xCAFEBABE); //2.寫入次版本號(hào) dout.writeShort(CLASSFILE_MINOR_VERSION); //3.寫入主版本號(hào) dout.writeShort(CLASSFILE_MAJOR_VERSION); //4.寫入常量池 cp.write(dout); //5.寫入訪問修飾符 dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER); //6.寫入類索引 dout.writeShort(cp.getClass(dotToSlash(className))); //7.寫入父類索引, 生成的代理類都繼承自Proxy dout.writeShort(cp.getClass(superclassName)); //8.寫入接口計(jì)數(shù)值 dout.writeShort(interfaces.length); //9.寫入接口集合 for (int i = 0; i < interfaces.length; i++) { dout.writeShort(cp.getClass(dotToSlash(interfaces[i].getName()))); } //10.寫入字段計(jì)數(shù)值 dout.writeShort(fields.size()); //11.寫入字段集合 for (FieldInfo f : fields) { f.write(dout); } //12.寫入方法計(jì)數(shù)值 dout.writeShort(methods.size()); //13.寫入方法集合 for (MethodInfo m : methods) { m.write(dout); } //14.寫入屬性計(jì)數(shù)值, 代理類class文件沒有屬性所以為0 dout.writeShort(0); } catch (IOException e) { throw new InternalError("unexpected I/O Exception"); } //轉(zhuǎn)換成二進(jìn)制數(shù)組輸出 return bout.toByteArray(); }
可以看到generateClassFile()方法是按照Class文件結(jié)構(gòu)進(jìn)行動(dòng)態(tài)拼接的。什么是Class文件呢?在這里我們先要說(shuō)明下,我們平時(shí)編寫的Java文件是以.java結(jié)尾的,在編寫好了之后通過(guò)編譯器進(jìn)行編譯會(huì)生成.class文件,這個(gè).class文件就是Class文件。Java程序的執(zhí)行只依賴于Class文件,和Java文件是沒有關(guān)系的。這個(gè)Class文件描述了一個(gè)類的信息,當(dāng)我們需要使用到一個(gè)類時(shí),Java虛擬機(jī)就會(huì)提前去加載這個(gè)類的Class文件并進(jìn)行初始化和相關(guān)的檢驗(yàn)工作,Java虛擬機(jī)能夠保證在你使用到這個(gè)類之前就會(huì)完成這些工作,我們只需要安心的去使用它就好了,而不必關(guān)心Java虛擬機(jī)是怎樣加載它的。當(dāng)然,Class文件并不一定非得通過(guò)編譯Java文件而來(lái),你甚至可以直接通過(guò)文本編輯器來(lái)編寫Class文件。在這里,JDK動(dòng)態(tài)代理就是通過(guò)程序來(lái)動(dòng)態(tài)生成Class文件的。我們?cè)俅位氐缴厦娴拇a中,可以看到,生成Class文件主要分為三步:
第一步:收集所有要生成的代理方法,將其包裝成ProxyMethod對(duì)象并注冊(cè)到Map集合中。
第二步:收集所有要為Class文件生成的字段信息和方法信息。
第三步:完成了上面的工作后,開始組裝Class文件。
我們知道一個(gè)類的核心部分就是它的字段和方法。我們重點(diǎn)聚焦第二步,看看它為代理類生成了哪些字段和方法。在第二步中,按順序做了下面四件事。
1.為代理類生成一個(gè)帶參構(gòu)造器,傳入InvocationHandler實(shí)例的引用并調(diào)用父類的帶參構(gòu)造器。
2.遍歷代理方法Map集合,為每個(gè)代理方法生成對(duì)應(yīng)的Method類型靜態(tài)域,并將其添加到fields集合中。
3.遍歷代理方法Map集合,為每個(gè)代理方法生成對(duì)應(yīng)的MethodInfo對(duì)象,并將其添加到methods集合中。
4.為代理類生成靜態(tài)初始化方法,該靜態(tài)初始化方法主要是將每個(gè)代理方法的引用賦值給對(duì)應(yīng)的靜態(tài)字段。
通過(guò)以上分析,我們可以大致知道JDK動(dòng)態(tài)代理最終會(huì)為我們生成如下結(jié)構(gòu)的代理類:
public class Proxy0 extends Proxy implements UserDao { //第一步, 生成構(gòu)造器 protected Proxy0(InvocationHandler h) { super(h); } //第二步, 生成靜態(tài)域 private static Method m1; //hashCode方法 private static Method m2; //equals方法 private static Method m3; //toString方法 private static Method m4; //... //第三步, 生成代理方法 @Override public int hashCode() { try { return (int) h.invoke(this, m1, null); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } @Override public boolean equals(Object obj) { try { Object[] args = new Object[] {obj}; return (boolean) h.invoke(this, m2, args); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } @Override public String toString() { try { return (String) h.invoke(this, m3, null); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } @Override public void save(User user) { try { //構(gòu)造參數(shù)數(shù)組, 如果有多個(gè)參數(shù)往后面添加就行了 Object[] args = new Object[] {user}; h.invoke(this, m4, args); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } //第四步, 生成靜態(tài)初始化方法 static { try { Class c1 = Class.forName(Object.class.getName()); Class c2 = Class.forName(UserDao.class.getName()); m1 = c1.getMethod("hashCode", null); m2 = c1.getMethod("equals", new Class[]{Object.class}); m3 = c1.getMethod("toString", null); m4 = c2.getMethod("save", new Class[]{User.class}); //... } catch (Exception e) { e.printStackTrace(); } } }
至此,經(jīng)過(guò)層層分析,深入探究JDK源碼,我們還原了動(dòng)態(tài)生成的代理類的本來(lái)面目,之前心中存在的一些疑問也隨之得到了很好的解釋
1.代理類默認(rèn)繼承Porxy類,因?yàn)镴ava中只支持單繼承,所以JDK動(dòng)態(tài)代理只能去實(shí)現(xiàn)接口。
2.代理方法都會(huì)去調(diào)用InvocationHandler的invoke()方法,因此我們需要重寫InvocationHandler的invoke()方法。
3.調(diào)用invoke()方法時(shí)會(huì)傳入代理實(shí)例本身,目標(biāo)方法和目標(biāo)方法參數(shù)。解釋了invoke()方法的參數(shù)是怎樣來(lái)的。
使用剛剛構(gòu)造出來(lái)的Proxy0作為代理類再次進(jìn)行測(cè)試,可以看到最終的結(jié)果與使用JDK動(dòng)態(tài)生成的代理類的效果是一樣的。再次驗(yàn)證了我們的分析是可靠且準(zhǔn)確的。至此,JDK動(dòng)態(tài)代理系列文章宣告結(jié)束。
關(guān)于“JDK動(dòng)態(tài)代理之ProxyGenerator如何生成代理類的字節(jié)碼文件”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。