溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JDK中如何解析ProxyGenerator生成代理類的字節(jié)碼文件

發(fā)布時(shí)間:2021-10-19 17:13:14 來源:億速云 閱讀:124 作者:柒染 欄目:大數(shù)據(jù)

JDK中如何解析ProxyGenerator生成代理類的字節(jié)碼文件,相信很多沒有經(jīng)驗(yàn)的人對(duì)此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。

通過前面幾篇的分析,我們知道代理類是通過Proxy類的ProxyClassFactory工廠生成的,這個(gè)工廠類會(huì)去調(diào)用ProxyGenerator類的generateProxyClass()方法來生成代理類的字節(jié)碼。ProxyGenerator這個(gè)類存放在sun.misc包下,我們可以通過OpenJDK源碼來找到這個(gè)類,該類的generateProxyClass()靜態(tài)方法的核心內(nèi)容就是去調(diào)用generateClassFile()實(shí)例方法來生成Class文件。我們直接來看generateClassFile()這個(gè)方法內(nèi)部做了些什么。


1 private byte[] generateClassFile() {
  2     //第一步, 將所有的方法組裝成ProxyMethod對(duì)象
  3     //首先為代理類生成toString, hashCode, equals等代理方法
  4     addProxyMethod(hashCodeMethod, Object.class);
  5     addProxyMethod(equalsMethod, Object.class);
  6     addProxyMethod(toStringMethod, Object.class);
  7     //遍歷每一個(gè)接口的每一個(gè)方法, 并且為其生成ProxyMethod對(duì)象
  8     for (int i = 0; i < interfaces.length; i++) {
  9         Method[] methods = interfaces[i].getMethods();
 10         for (int j = 0; j < methods.length; j++) {
 11             addProxyMethod(methods[j], interfaces[i]);
 12         }
 13     }
 14     //對(duì)于具有相同簽名的代理方法, 檢驗(yàn)方法的返回值是否兼容
 15     for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
 16         checkReturnTypes(sigmethods);
 17     }
 18     
 19     //第二步, 組裝要生成的class文件的所有的字段信息和方法信息
 20     try {
 21         //添加構(gòu)造器方法
 22         methods.add(generateConstructor());
 23         //遍歷緩存中的代理方法
 24         for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
 25             for (ProxyMethod pm : sigmethods) {
 26                 //添加代理類的靜態(tài)字段, 例如:private static Method m1;
 27                 fields.add(new FieldInfo(pm.methodFieldName,
 28                         "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC));
 29                 //添加代理類的代理方法
 30                 methods.add(pm.generateMethod());
 31             }
 32         }
 33         //添加代理類的靜態(tài)字段初始化方法
 34         methods.add(generateStaticInitializer());
 35     } catch (IOException e) {
 36         throw new InternalError("unexpected I/O Exception");
 37     }
 38     
 39     //驗(yàn)證方法和字段集合不能大于65535
 40     if (methods.size() > 65535) {
 41         throw new IllegalArgumentException("method limit exceeded");
 42     }
 43     if (fields.size() > 65535) {
 44         throw new IllegalArgumentException("field limit exceeded");
 45     }
 46 
 47     //第三步, 寫入最終的class文件
 48     //驗(yàn)證常量池中存在代理類的全限定名
 49     cp.getClass(dotToSlash(className));
 50     //驗(yàn)證常量池中存在代理類父類的全限定名, 父類名為:"java/lang/reflect/Proxy"
 51     cp.getClass(superclassName);
 52     //驗(yàn)證常量池存在代理類接口的全限定名
 53     for (int i = 0; i < interfaces.length; i++) {
 54         cp.getClass(dotToSlash(interfaces[i].getName()));
 55     }
 56     //接下來要開始寫入文件了,設(shè)置常量池只讀
 57     cp.setReadOnly();
 58     
 59     ByteArrayOutputStream bout = new ByteArrayOutputStream();
 60     DataOutputStream dout = new DataOutputStream(bout);
 61     try {
 62         //1.寫入魔數(shù)
 63         dout.writeInt(0xCAFEBABE);
 64         //2.寫入次版本號(hào)
 65         dout.writeShort(CLASSFILE_MINOR_VERSION);
 66         //3.寫入主版本號(hào)
 67         dout.writeShort(CLASSFILE_MAJOR_VERSION);
 68         //4.寫入常量池
 69         cp.write(dout);
 70         //5.寫入訪問修飾符
 71         dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
 72         //6.寫入類索引
 73         dout.writeShort(cp.getClass(dotToSlash(className)));
 74         //7.寫入父類索引, 生成的代理類都繼承自Proxy
 75         dout.writeShort(cp.getClass(superclassName));
 76         //8.寫入接口計(jì)數(shù)值
 77         dout.writeShort(interfaces.length);
 78         //9.寫入接口集合
 79         for (int i = 0; i < interfaces.length; i++) {
 80             dout.writeShort(cp.getClass(dotToSlash(interfaces[i].getName())));
 81         }
 82         //10.寫入字段計(jì)數(shù)值
 83         dout.writeShort(fields.size());
 84         //11.寫入字段集合 
 85         for (FieldInfo f : fields) {
 86             f.write(dout);
 87         }
 88         //12.寫入方法計(jì)數(shù)值
 89         dout.writeShort(methods.size());
 90         //13.寫入方法集合
 91         for (MethodInfo m : methods) {
 92             m.write(dout);
 93         }
 94         //14.寫入屬性計(jì)數(shù)值, 代理類class文件沒有屬性所以為0
 95         dout.writeShort(0);
 96     } catch (IOException e) {
 97         throw new InternalError("unexpected I/O Exception");
 98     }
 99     //轉(zhuǎn)換成二進(jìn)制數(shù)組輸出
100     return bout.toByteArray();
101 }


可以看到generateClassFile()方法是按照Class文件結(jié)構(gòu)進(jìn)行動(dòng)態(tài)拼接的。什么是Class文件呢?在這里我們先要說明下,我們平時(shí)編寫的Java文件是以.java結(jié)尾的,在編寫好了之后通過編譯器進(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文件并不一定非得通過編譯Java文件而來,你甚至可以直接通過文本編輯器來編寫Class文件。在這里,JDK動(dòng)態(tà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)字段。

通過以上分析,我們可以大致知道JDK動(dòng)態(tài)代理最終會(huì)為我們生成如下結(jié)構(gòu)的代理類:


1 public class Proxy0 extends Proxy implements UserDao {
 2 
 3     //第一步, 生成構(gòu)造器
 4     protected Proxy0(InvocationHandler h) {
 5         super(h);
 6     }
 7 
 8     //第二步, 生成靜態(tài)域
 9     private static Method m1;   //hashCode方法
10     private static Method m2;   //equals方法
11     private static Method m3;   //toString方法
12     private static Method m4;   //...
13     
14     //第三步, 生成代理方法
15     @Override
16     public int hashCode() {
17         try {
18             return (int) h.invoke(this, m1, null);
19         } catch (Throwable e) {
20             throw new UndeclaredThrowableException(e);
21         }
22     }
23     
24     @Override
25     public boolean equals(Object obj) {
26         try {
27             Object[] args = new Object[] {obj};
28             return (boolean) h.invoke(this, m2, args);
29         } catch (Throwable e) {
30             throw new UndeclaredThrowableException(e);
31         }
32     }
33     
34     @Override
35     public String toString() {
36         try {
37             return (String) h.invoke(this, m3, null);
38         } catch (Throwable e) {
39             throw new UndeclaredThrowableException(e);
40         }
41     }
42     
43     @Override
44     public void save(User user) {
45         try {
46             //構(gòu)造參數(shù)數(shù)組, 如果有多個(gè)參數(shù)往后面添加就行了
47             Object[] args = new Object[] {user};
48             h.invoke(this, m4, args);
49         } catch (Throwable e) {
50             throw new UndeclaredThrowableException(e);
51         }
52     }
53     
54     //第四步, 生成靜態(tài)初始化方法
55     static {
56         try {
57             Class c1 = Class.forName(Object.class.getName());
58             Class c2 = Class.forName(UserDao.class.getName());    
59             m1 = c1.getMethod("hashCode", null);
60             m2 = c1.getMethod("equals", new Class[]{Object.class});
61             m3 = c1.getMethod("toString", null);
62             m4 = c2.getMethod("save", new Class[]{User.class});
63             //...
64         } catch (Exception e) {
65             e.printStackTrace();
66         }
67     }
68     
69 }

至此,經(jīng)過層層分析,深入探究JDK源碼,我們還原了動(dòng)態(tà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ù)是怎樣來的。

JDK中如何解析ProxyGenerator生成代理類的字節(jié)碼文件

使用剛剛構(gòu)造出來的Proxy0作為代理類再次進(jìn)行測(cè)試,可以看到最終的結(jié)果與使用JDK動(dòng)態(tài)生成的代理類的效果是一樣的。再次驗(yàn)證了我們的分析是可靠且準(zhǔn)確的。

看完上述內(nèi)容,你們掌握J(rèn)DK中如何解析ProxyGenerator生成代理類的字節(jié)碼文件的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI