一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - Java教程 - Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

2020-11-03 22:50luanlouis Java教程

這篇文章主要為大家詳細介紹了Java動態(tài)代理機制,具有一定的參考價值,感興趣的小伙伴們可以參考一下

class文件簡介及加載

java編譯器編譯好java文件之后,產(chǎn)生.class 文件在磁盤中。這種class文件是二進制文件,內(nèi)容是只有jvm虛擬機能夠識別的機器碼。jvm虛擬機讀取字節(jié)碼文件,取出二進制數(shù)據(jù),加載到內(nèi)存中,解析.class 文件內(nèi)的信息,生成對應(yīng)的 class對象:

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

class字節(jié)碼文件是根據(jù)jvm虛擬機規(guī)范中規(guī)定的字節(jié)碼組織規(guī)則生成的、具體class文件是怎樣組織類信息的,可以參考 此博文:深入理解java class文件格式系列。或者是java虛擬機規(guī)范。

下面通過一段代碼演示手動加載 class文件字節(jié)碼到系統(tǒng)內(nèi),轉(zhuǎn)換成class對象,然后再實例化的過程:

a. 定義一個 programmer類:

?
1
2
3
4
5
6
7
8
9
10
11
12
package samples;
/**
 * 程序猿類
 * @author louluan
 */
public class programmer {
 
 public void code()
 {
  system.out.println("i'm a programmer,just coding.....");
 }
}

b. 自定義一個類加載器:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
package samples;
/**
 * 自定義一個類加載器,用于將字節(jié)碼轉(zhuǎn)換為class對象
 * @author louluan
 */
public class myclassloader extends classloader {
 
 public class<?> definemyclass( byte[] b, int off, int len)
 {
  return super.defineclass(b, off, len);
 }
  
}

c. 然后編譯成programmer.class文件,在程序中讀取字節(jié)碼,然后轉(zhuǎn)換成相應(yīng)的class對象,再實例化:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package samples;
 
import java.io.file;
import java.io.fileinputstream;
import java.io.filenotfoundexception;
import java.io.ioexception;
import java.io.inputstream;
import java.net.url;
 
public class mytest {
 
 public static void main(string[] args) throws ioexception {
  //讀取本地的class文件內(nèi)的字節(jié)碼,轉(zhuǎn)換成字節(jié)碼數(shù)組
  file file = new file(".");
  inputstream input = new fileinputstream(file.getcanonicalpath()+"\\bin\\samples\\programmer.class");
  byte[] result = new byte[1024];
   
  int count = input.read(result);
  // 使用自定義的類加載器將 byte字節(jié)碼數(shù)組轉(zhuǎn)換為對應(yīng)的class對象
  myclassloader loader = new myclassloader();
  class clazz = loader.definemyclass( result, 0, count);
  //測試加載是否成功,打印class 對象的名稱
  system.out.println(clazz.getcanonicalname());
     
    //實例化一個programmer對象
    object o= clazz.newinstance();
    try {
     //調(diào)用programmer的code方法
     clazz.getmethod("code", null).invoke(o, null);
     } catch (illegalargumentexception | invocationtargetexception
      | nosuchmethodexception | securityexception e) {
      e.printstacktrace();
     }
 }
}

以上代碼演示了,通過字節(jié)碼加載成class 對象的能力,下面看一下在代碼中如何生成class文件的字節(jié)碼。

在運行期的代碼中生成二進制字節(jié)碼

由于jvm通過字節(jié)碼的二進制信息加載類的,那么,如果我們在運行期系統(tǒng)中,遵循java編譯系統(tǒng)組織.class文件的格式和結(jié)構(gòu),生成相應(yīng)的二進制數(shù)據(jù),然后再把這個二進制數(shù)據(jù)加載轉(zhuǎn)換成對應(yīng)的類,這樣,就完成了在代碼中,動態(tài)創(chuàng)建一個類的能力了。

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

在運行時期可以按照java虛擬機規(guī)范對class文件的組織規(guī)則生成對應(yīng)的二進制字節(jié)碼。當前有很多開源框架可以完成這些功能,如asm,javassist。

java字節(jié)碼生成開源框架介紹--asm:

asm 是一個 java 字節(jié)碼操控框架。它能夠以二進制形式修改已有類或者動態(tài)生成類。asm 可以直接產(chǎn)生二進制 class 文件,也可以在類被加載入 java 虛擬機之前動態(tài)改變類行為。asm 從類文件中讀入信息后,能夠改變類行為,分析類信息,甚至能夠根據(jù)用戶要求生成新類。

不過asm在創(chuàng)建class字節(jié)碼的過程中,操縱的級別是底層jvm的匯編指令級別,這要求asm使用者要對class組織結(jié)構(gòu)和jvm匯編指令有一定的了解。

下面通過asm 生成下面類programmer的class字節(jié)碼:

?
1
2
3
4
5
6
7
8
9
10
package com.samples;
import java.io.printstream;
 
public class programmer {
 
 public void code()
 {
  system.out.println("i'm a programmer,just coding.....");
 }
}

使用asm框架提供了classwriter 接口,通過訪問者模式進行動態(tài)創(chuàng)建class字節(jié)碼,看下面的例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package samples;
 
import java.io.file;
import java.io.fileoutputstream;
import java.io.ioexception;
 
import org.objectweb.asm.classwriter;
import org.objectweb.asm.methodvisitor;
import org.objectweb.asm.opcodes;
public class mygenerator {
 
 public static void main(string[] args) throws ioexception {
 
  system.out.println();
  classwriter classwriter = new classwriter(0);
  // 通過visit方法確定類的頭部信息
  classwriter.visit(opcodes.v1_7,// java版本
    opcodes.acc_public,// 類修飾符
    "programmer", // 類的全限定名
    null, "java/lang/object", null);
   
  //創(chuàng)建構(gòu)造函數(shù)
  methodvisitor mv = classwriter.visitmethod(opcodes.acc_public, "<init>", "()v", null, null);
  mv.visitcode();
  mv.visitvarinsn(opcodes.aload, 0);
  mv.visitmethodinsn(opcodes.invokespecial, "java/lang/object", "<init>","()v");
  mv.visitinsn(opcodes.return);
  mv.visitmaxs(1, 1);
  mv.visitend();
   
  // 定義code方法
  methodvisitor methodvisitor = classwriter.visitmethod(opcodes.acc_public, "code", "()v",
    null, null);
  methodvisitor.visitcode();
  methodvisitor.visitfieldinsn(opcodes.getstatic, "java/lang/system", "out",
    "ljava/io/printstream;");
  methodvisitor.visitldcinsn("i'm a programmer,just coding.....");
  methodvisitor.visitmethodinsn(opcodes.invokevirtual, "java/io/printstream", "println",
    "(ljava/lang/string;)v");
  methodvisitor.visitinsn(opcodes.return);
  methodvisitor.visitmaxs(2, 2);
  methodvisitor.visitend();
  classwriter.visitend();
  // 使classwriter類已經(jīng)完成
  // 將classwriter轉(zhuǎn)換成字節(jié)數(shù)組寫到文件里面去
  byte[] data = classwriter.tobytearray();
  file file = new file("d://programmer.class");
  fileoutputstream fout = new fileoutputstream(file);
  fout.write(data);
  fout.close();
 }
}

上述的代碼執(zhí)行過后,用java反編譯工具(如jd_gui)打開d盤下生成的programmer.class,可以看到以下信息:

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

再用上面我們定義的類加載器將這個class文件加載到內(nèi)存中,然后 創(chuàng)建class對象,并且實例化一個對象,調(diào)用code方法,會看到下面的結(jié)果:

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

 以上表明:在代碼里生成字節(jié)碼,并動態(tài)地加載成class對象、創(chuàng)建實例是完全可以實現(xiàn)的。

java字節(jié)碼生成開源框架介紹--javassist:

javassist是一個開源的分析、編輯和創(chuàng)建java字節(jié)碼的類庫。是由東京工業(yè)大學(xué)的數(shù)學(xué)和計算機科學(xué)系的 shigeru chiba (千葉 滋)所創(chuàng)建的。它已加入了開放源代碼jboss 應(yīng)用服務(wù)器項目,通過使用javassist對字節(jié)碼操作為jboss實現(xiàn)動態(tài)aop框架。javassist是jboss的一個子項目,其主要的優(yōu)點,在于簡單,而且快速。直接使用java編碼的形式,而不需要了解虛擬機指令,就能動態(tài)改變類的結(jié)構(gòu),或者動態(tài)生成類。
下面通過javassist創(chuàng)建上述的programmer類:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import javassist.classpool;
import javassist.ctclass;
import javassist.ctmethod;
import javassist.ctnewmethod;
 
public class mygenerator {
 
 public static void main(string[] args) throws exception {
  classpool pool = classpool.getdefault();
  //創(chuàng)建programmer類 
  ctclass cc= pool.makeclass("com.samples.programmer");
  //定義code方法
  ctmethod method = ctnewmethod.make("public void code(){}", cc);
  //插入方法代碼
  method.insertbefore("system.out.println(\"i'm a programmer,just coding.....\");");
  cc.addmethod(method);
  //保存生成的字節(jié)碼
  cc.writefile("d://temp");
 }
}

通過jd-gui反編譯工具打開programmer.class 可以看到以下代碼:

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

代理的基本構(gòu)成:

代理模式上,基本上有subject角色,realsubject角色,proxy角色。其中:subject角色負責定義realsubject和proxy角色應(yīng)該實現(xiàn)的接口;realsubject角色用來真正完成業(yè)務(wù)服務(wù)功能;proxy角色負責將自身的request請求,調(diào)用realsubject 對應(yīng)的request功能來實現(xiàn)業(yè)務(wù)功能,自己不真正做業(yè)務(wù)。

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

上面的這幅代理結(jié)構(gòu)圖是典型的靜態(tài)的代理模式:

當在代碼階段規(guī)定這種代理關(guān)系,proxy類通過編譯器編譯成class文件,當系統(tǒng)運行時,此class已經(jīng)存在了。這種靜態(tài)的代理模式固然在訪問無法訪問的資源,增強現(xiàn)有的接口業(yè)務(wù)功能方面有很大的優(yōu)點,但是大量使用這種靜態(tài)代理,會使我們系統(tǒng)內(nèi)的類的規(guī)模增大,并且不易維護;并且由于proxy和realsubject的功能 本質(zhì)上是相同的,proxy只是起到了中介的作用,這種代理在系統(tǒng)中的存在,導(dǎo)致系統(tǒng)結(jié)構(gòu)比較臃腫和松散。

為了解決這個問題,就有了動態(tài)地創(chuàng)建proxy的想法:在運行狀態(tài)中,需要代理的地方,根據(jù)subject 和realsubject,動態(tài)地創(chuàng)建一個proxy,用完之后,就會銷毀,這樣就可以避免了proxy 角色的class在系統(tǒng)中冗雜的問題了。
下面以一個代理模式實例闡述這一問題:

將車站的售票服務(wù)抽象出一個接口ticketservice,包含問詢,賣票,退票功能,車站類station實現(xiàn)了ticketservice接口,車票代售點stationproxy則實現(xiàn)了代理角色的功能,類圖如下所示。

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

對應(yīng)的靜態(tài)的代理模式代碼如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.foo.proxy;
 
/**
 * 售票服務(wù)接口實現(xiàn)類,車站
 * @author louluan
 */
public class station implements ticketservice {
 
 @override
 public void sellticket() {
  system.out.println("\n\t售票.....\n");
 }
 
 @override
 public void inquire() {
  system.out.println("\n\t問詢。。。。\n");
 }
 
 @override
 public void withdraw() {
  system.out.println("\n\t退票......\n");
 }
 
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.foo.proxy;
/**
 * 售票服務(wù)接口
 * @author louluan
 */
public interface ticketservice {
 
 //售票
 public void sellticket();
  
 //問詢
 public void inquire();
  
 //退票
 public void withdraw();
  
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.foo.proxy;
 
/**
 * 車票代售點
 * @author louluan
 *
 */
public class stationproxy implements ticketservice {
 
 private station station;
 
 public stationproxy(station station){
  this.station = station;
 }
  
 @override
 public void sellticket() {
 
  // 1.做真正業(yè)務(wù)前,提示信息
  this.showalertinfo("××××您正在使用車票代售點進行購票,每張票將會收取5元手續(xù)費!××××");
  // 2.調(diào)用真實業(yè)務(wù)邏輯
  station.sellticket();
  // 3.后處理
  this.takehandlingfee();
  this.showalertinfo("××××歡迎您的光臨,再見!××××\n");
 
 }
 
 @override
 public void inquire() {
  // 1做真正業(yè)務(wù)前,提示信息
  this.showalertinfo("××××歡迎光臨本代售點,問詢服務(wù)不會收取任何費用,本問詢信息僅供參考,具體信息以車站真實數(shù)據(jù)為準!××××");
  // 2.調(diào)用真實邏輯
  station.inquire();
  // 3。后處理
  this.showalertinfo("××××歡迎您的光臨,再見!××××\n");
 }
 
 @override
 public void withdraw() {
  // 1。真正業(yè)務(wù)前處理
  this.showalertinfo("××××歡迎光臨本代售點,退票除了扣除票額的20%外,本代理處額外加收2元手續(xù)費!××××");
  // 2.調(diào)用真正業(yè)務(wù)邏輯
  station.withdraw();
  // 3.后處理
  this.takehandlingfee();
 
 }
 
 /*
  * 展示額外信息
  */
 private void showalertinfo(string info) {
  system.out.println(info);
 }
 
 /*
  * 收取手續(xù)費
  */
 private void takehandlingfee() {
  system.out.println("收取手續(xù)費,打印發(fā)票。。。。。\n");
 }
 
}

由于我們現(xiàn)在不希望靜態(tài)地有stationproxy類存在,希望在代碼中,動態(tài)生成器二進制代碼,加載進來。為此,使用javassist開源框架,在代碼中動態(tài)地生成stationproxy的字節(jié)碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.foo.proxy;
 
import java.lang.reflect.constructor;
 
import javassist.*;
public class test {
 
 public static void main(string[] args) throws exception {
  createproxy();
 }
  
 /*
  * 手動創(chuàng)建字節(jié)碼
  */
 private static void createproxy() throws exception
 {
  classpool pool = classpool.getdefault();
 
  ctclass cc = pool.makeclass("com.foo.proxy.stationproxy");
   
  //設(shè)置接口
  ctclass interface1 = pool.get("com.foo.proxy.ticketservice");
  cc.setinterfaces(new ctclass[]{interface1});
   
  //設(shè)置field
  ctfield field = ctfield.make("private com.foo.proxy.station station;", cc);
   
  cc.addfield(field);
   
  ctclass stationclass = pool.get("com.foo.proxy.station");
  ctclass[] arrays = new ctclass[]{stationclass};
  ctconstructor ctc = ctnewconstructor.make(arrays,null,ctnewconstructor.pass_none,null,null, cc);
  //設(shè)置構(gòu)造函數(shù)內(nèi)部信息
  ctc.setbody("{this.station=$1;}");
  cc.addconstructor(ctc);
 
  //創(chuàng)建收取手續(xù) takehandlingfee方法
  ctmethod takehandlingfee = ctmethod.make("private void takehandlingfee() {}", cc);
  takehandlingfee.setbody("system.out.println(\"收取手續(xù)費,打印發(fā)票。。。。。\");");
  cc.addmethod(takehandlingfee);
   
  //創(chuàng)建showalertinfo 方法
  ctmethod showinfo = ctmethod.make("private void showalertinfo(string info) {}", cc);
  showinfo.setbody("system.out.println($1);");
  cc.addmethod(showinfo);
   
  //sellticket
  ctmethod sellticket = ctmethod.make("public void sellticket(){}", cc);
  sellticket.setbody("{this.showalertinfo(\"××××您正在使用車票代售點進行購票,每張票將會收取5元手續(xù)費!××××\");"
    + "station.sellticket();"
    + "this.takehandlingfee();"
    + "this.showalertinfo(\"××××歡迎您的光臨,再見!××××\");}");
  cc.addmethod(sellticket);
   
  //添加inquire方法
  ctmethod inquire = ctmethod.make("public void inquire() {}", cc);
  inquire.setbody("{this.showalertinfo(\"××××歡迎光臨本代售點,問詢服務(wù)不會收取任何費用,本問詢信息僅供參考,具體信息以車站真實數(shù)據(jù)為準!××××\");"
  + "station.inquire();"
  + "this.showalertinfo(\"××××歡迎您的光臨,再見!××××\");}"
  );
  cc.addmethod(inquire);
   
  //添加widthraw方法
  ctmethod withdraw = ctmethod.make("public void withdraw() {}", cc);
  withdraw.setbody("{this.showalertinfo(\"××××歡迎光臨本代售點,退票除了扣除票額的20%外,本代理處額外加收2元手續(xù)費!××××\");"
    + "station.withdraw();"
    + "this.takehandlingfee();}"
    );
  cc.addmethod(withdraw);
   
  //獲取動態(tài)生成的class
  class c = cc.toclass();
  //獲取構(gòu)造器
  constructor constructor= c.getconstructor(station.class);
  //通過構(gòu)造器實例化
  ticketservice o = (ticketservice)constructor.newinstance(new station());
  o.inquire();
   
  cc.writefile("d://test");
 }
  
}

上述代碼執(zhí)行過后,會產(chǎn)生stationproxy的字節(jié)碼,并且用生成字節(jié)碼加載如內(nèi)存創(chuàng)建對象,調(diào)用inquire()方法,會得到以下結(jié)果:

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

通過上面動態(tài)生成的代碼,我們發(fā)現(xiàn),其實現(xiàn)相當?shù)芈闊┰趧?chuàng)造的過程中,含有太多的業(yè)務(wù)代碼。我們使用上述創(chuàng)建proxy代理類的方式的初衷是減少系統(tǒng)代碼的冗雜度,但是上述做法卻增加了在動態(tài)創(chuàng)建代理類過程中的復(fù)雜度:手動地創(chuàng)建了太多的業(yè)務(wù)代碼,并且封裝性也不夠,完全不具有可拓展性和通用性。如果某個代理類的一些業(yè)務(wù)邏輯非常復(fù)雜,上述的動態(tài)創(chuàng)建代理的方式是非常不可取的!

invocationhandler角色的由來

仔細思考代理模式中的代理proxy角色。proxy角色在執(zhí)行代理業(yè)務(wù)的時候,無非是在調(diào)用真正業(yè)務(wù)之前或者之后做一些“額外”業(yè)務(wù)。

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

有上圖可以看出,代理類處理的邏輯很簡單:在調(diào)用某個方法前及方法后做一些額外的業(yè)務(wù)。換一種思路就是:在觸發(fā)(invoke)真實角色的方法之前或者之后做一些額外的業(yè)務(wù)。那么,為了構(gòu)造出具有通用性和簡單性的代理類,可以將所有的觸發(fā)真實角色動作交給一個觸發(fā)的管理器,讓這個管理器統(tǒng)一地管理觸發(fā)。這種管理器就是invocation handler。
動態(tài)代理模式的結(jié)構(gòu)跟上面的靜態(tài)代理模式稍微有所不同,多引入了一個invocationhandler角色。

先解釋一下invocationhandler的作用:

在靜態(tài)代理中,代理proxy中的方法,都指定了調(diào)用了特定的realsubject中的對應(yīng)的方法:

在上面的靜態(tài)代理模式下,proxy所做的事情,無非是調(diào)用在不同的request時,調(diào)用觸發(fā)realsubject對應(yīng)的方法;更抽象點看,proxy所作的事情;在java中 方法(method)也是作為一個對象來看待了,動態(tài)代理工作的基本模式就是將自己的方法功能的實現(xiàn)交給 invocationhandler角色,外界對proxy角色中的每一個方法的調(diào)用,proxy角色都會交給invocationhandler來處理,而invocationhandler則調(diào)用具體對象角色的方法。如下圖所示:

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

在這種模式之中:代理proxy 和realsubject 應(yīng)該實現(xiàn)相同的功能,這一點相當重要。(我這里說的功能,可以理解為某個類的public方法)

在面向?qū)ο蟮木幊讨校绻覀兿胍s定proxy 和realsubject可以實現(xiàn)相同的功能,有兩種方式:

    a.一個比較直觀的方式,就是定義一個功能接口,然后讓proxy 和realsubject來實現(xiàn)這個接口。
    b.還有比較隱晦的方式,就是通過繼承。因為如果proxy 繼承自realsubject,這樣proxy則擁有了realsubject的功能,proxy還可以通過重寫realsubject中的方法,來實現(xiàn)多態(tài)。

其中jdk中提供的創(chuàng)建動態(tài)代理的機制,是以a 這種思路設(shè)計的,而cglib 則是以b思路設(shè)計的。

jdk的動態(tài)代理創(chuàng)建機制----通過接口

比如現(xiàn)在想為realsubject這個類創(chuàng)建一個動態(tài)代理對象,jdk主要會做以下工作:

    1.   獲取 realsubject上的所有接口列表;
    2.   確定要生成的代理類的類名,默認為:com.sun.proxy.$proxyxxxx ;
    3.   根據(jù)需要實現(xiàn)的接口信息,在代碼中動態(tài)創(chuàng)建 該proxy類的字節(jié)碼;
    4 .  將對應(yīng)的字節(jié)碼轉(zhuǎn)換為對應(yīng)的class 對象;
    5.   創(chuàng)建invocationhandler 實例handler,用來處理proxy所有方法調(diào)用;
    6.   proxy 的class對象 以創(chuàng)建的handler對象為參數(shù),實例化一個proxy對象

jdk通過 java.lang.reflect.proxy包來支持動態(tài)代理,一般情況下,我們使用下面的newproxyinstance方法

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

而對于invocationhandler,我們需要實現(xiàn)下列的invoke方法:
在調(diào)用代理對象中的每一個方法時,在代碼內(nèi)部,都是直接調(diào)用了invocationhandler 的invoke方法,而invoke方法根據(jù)代理類傳遞給自己的method參數(shù)來區(qū)分是什么方法。

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

講的有點抽象,下面通過一個實例來演示一下吧:

jdk動態(tài)代理示例

現(xiàn)在定義兩個接口vehicle和rechargable,vehicle表示交通工具類,有drive()方法;rechargable接口表示可充電的(工具),有recharge() 方法;

定義一個實現(xiàn)兩個接口的類electriccar,類圖如下:

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理     

通過下面的代碼片段,來為electriccar創(chuàng)建動態(tài)代理類:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.foo.proxy;
 
import java.lang.reflect.invocationhandler;
import java.lang.reflect.proxy;
 
public class test {
 
 public static void main(string[] args) {
 
  electriccar car = new electriccar();
  // 1.獲取對應(yīng)的classloader
  classloader classloader = car.getclass().getclassloader();
 
  // 2.獲取electriccar 所實現(xiàn)的所有接口
  class[] interfaces = car.getclass().getinterfaces();
  // 3.設(shè)置一個來自代理傳過來的方法調(diào)用請求處理器,處理所有的代理對象上的方法調(diào)用
  invocationhandler handler = new invocationhandlerimpl(car);
  /*
   4.根據(jù)上面提供的信息,創(chuàng)建代理對象 在這個過程中,
       a.jdk會通過根據(jù)傳入的參數(shù)信息動態(tài)地在內(nèi)存中創(chuàng)建和.class 文件等同的字節(jié)碼
     b.然后根據(jù)相應(yīng)的字節(jié)碼轉(zhuǎn)換成對應(yīng)的class,
       c.然后調(diào)用newinstance()創(chuàng)建實例
   */
  object o = proxy.newproxyinstance(classloader, interfaces, handler);
  vehicle vehicle = (vehicle) o;
  vehicle.drive();
  rechargable rechargeable = (rechargable) o;
  rechargeable.recharge();
 }
}
?
1
2
3
4
5
6
7
8
package com.foo.proxy;
/**
 * 交通工具接口
 * @author louluan
 */
public interface vehicle {
 public void drive();
}
?
1
2
3
4
5
6
7
8
9
package com.foo.proxy;
/**
 * 可充電設(shè)備接口
 * @author louluan
 */
public interface rechargable {
 
 public void recharge();
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.foo.proxy;
/**
 * 電能車類,實現(xiàn)rechargable,vehicle接口
 * @author louluan
 */
public class electriccar implements rechargable, vehicle {
 
 @override
 public void drive() {
  system.out.println("electric car is moving silently...");
 }
 
 @override
 public void recharge() {
  system.out.println("electric car is recharging...");
 }
 
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.foo.proxy;
 
import java.lang.reflect.invocationhandler;
import java.lang.reflect.method;
 
public class invocationhandlerimpl implements invocationhandler {
 
 private electriccar car;
  
 public invocationhandlerimpl(electriccar car)
 {
  this.car=car;
 }
  
 @override
 public object invoke(object paramobject, method parammethod,
   object[] paramarrayofobject) throws throwable {
  system.out.println("you are going to invoke "+parammethod.getname()+" ...");
  parammethod.invoke(car, null);
  system.out.println(parammethod.getname()+" invocation has been finished...");
  return null;
 }
 
}

來看一下代碼執(zhí)行后的結(jié)果:

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

 生成動態(tài)代理類的字節(jié)碼并且保存到硬盤中: 
jdk提供了sun.misc.proxygenerator.generateproxyclass(string proxyname,class[] interfaces) 底層方法來產(chǎn)生動態(tài)代理類的字節(jié)碼:
下面定義了一個工具類,用來將生成的動態(tài)代理類保存到硬盤中:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.foo.proxy;
 
import java.io.fileoutputstream;
import java.io.ioexception;
import java.lang.reflect.proxy;
import sun.misc.proxygenerator;
 
public class proxyutils {
 
 /*
  * 將根據(jù)類信息 動態(tài)生成的二進制字節(jié)碼保存到硬盤中,
  * 默認的是clazz目錄下
   * params :clazz 需要生成動態(tài)代理類的類
   * proxyname : 為動態(tài)生成的代理類的名稱
   */
 public static void generateclassfile(class clazz,string proxyname)
 {
  //根據(jù)類信息和提供的代理類名稱,生成字節(jié)碼
    byte[] classfile = proxygenerator.generateproxyclass(proxyname, clazz.getinterfaces());
  string paths = clazz.getresource(".").getpath();
  system.out.println(paths);
  fileoutputstream out = null;
   
  try {
   //保留到硬盤中
   out = new fileoutputstream(paths+proxyname+".class");
   out.write(classfile);
   out.flush();
  } catch (exception e) {
   e.printstacktrace();
  } finally {
   try {
    out.close();
   } catch (ioexception e) {
    e.printstacktrace();
   }
  }
 }
  
}

現(xiàn)在我們想將生成的代理類起名為“electriccarproxy”,并保存在硬盤,應(yīng)該使用以下語句:

proxyutils.generateclassfile(car.getclass(), "electriccarproxy"); 

這樣將在electriccar.class 同級目錄下產(chǎn)生 electriccarproxy.class文件。用反編譯工具如jd-gui.exe 打開,將會看到以下信息:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import com.foo.proxy.rechargable;
import com.foo.proxy.vehicle;
import java.lang.reflect.invocationhandler;
import java.lang.reflect.method;
import java.lang.reflect.proxy;
import java.lang.reflect.undeclaredthrowableexception;
/**
 生成的動態(tài)代理類的組織模式是繼承proxy類,然后實現(xiàn)需要實現(xiàn)代理的類上的所有接口,而在實現(xiàn)的過程中,則是通過將所有的方法都交給了invocationhandler來處理
*/
 public final class electriccarproxy extends proxy
 implements rechargable, vehicle
{
 private static method m1;
 private static method m3;
 private static method m4;
 private static method m0;
 private static method m2;
 
 public electriccarproxy(invocationhandler paraminvocationhandler)
 throws
 {
 super(paraminvocationhandler);
 }
 
 public final boolean equals(object paramobject)
 throws
 {
 try
 { // 方法功能實現(xiàn)交給invocationhandler處理
  return ((boolean)this.h.invoke(this, m1, new object[] { paramobject })).booleanvalue();
 }
 catch (error|runtimeexception localerror)
 {
  throw localerror;
 }
 catch (throwable localthrowable)
 {
  throw new undeclaredthrowableexception(localthrowable);
 }
 }
 
 public final void recharge()
 throws
 {
 try
 {
 
  // 方法功能實現(xiàn)交給invocationhandler處理
 
  this.h.invoke(this, m3, null);
  return;
 }
 catch (error|runtimeexception localerror)
 {
  throw localerror;
 }
 catch (throwable localthrowable)
 {
  throw new undeclaredthrowableexception(localthrowable);
 }
 }
 
 public final void drive()
 throws
 {
 try
 {
 
  // 方法功能實現(xiàn)交給invocationhandler處理
 
  this.h.invoke(this, m4, null);
  return;
 }
 catch (error|runtimeexception localerror)
 {
  throw localerror;
 }
 catch (throwable localthrowable)
 {
  throw new undeclaredthrowableexception(localthrowable);
 }
 }
 
 public final int hashcode()
 throws
 {
 try
 {
 
  // 方法功能實現(xiàn)交給invocationhandler處理
 
  return ((integer)this.h.invoke(this, m0, null)).intvalue();
 }
 catch (error|runtimeexception localerror)
 {
  throw localerror;
 }
 catch (throwable localthrowable)
 {
  throw new undeclaredthrowableexception(localthrowable);
 }
 }
 
 public final string tostring()
 throws
 {
 try
 {
 
  // 方法功能實現(xiàn)交給invocationhandler處理
  return (string)this.h.invoke(this, m2, null);
 }
 catch (error|runtimeexception localerror)
 {
  throw localerror;
 }
 catch (throwable localthrowable)
 {
  throw new undeclaredthrowableexception(localthrowable);
 }
 }
 
 static
 {
 try
 { //為每一個需要方法對象,當調(diào)用相應(yīng)的方法時,分別將方法對象作為參數(shù)傳遞給invocationhandler處理
  m1 = class.forname("java.lang.object").getmethod("equals", new class[] { class.forname("java.lang.object") });
  m3 = class.forname("com.foo.proxy.rechargable").getmethod("recharge", new class[0]);
  m4 = class.forname("com.foo.proxy.vehicle").getmethod("drive", new class[0]);
  m0 = class.forname("java.lang.object").getmethod("hashcode", new class[0]);
  m2 = class.forname("java.lang.object").getmethod("tostring", new class[0]);
  return;
 }
 catch (nosuchmethodexception localnosuchmethodexception)
 {
  throw new nosuchmethoderror(localnosuchmethodexception.getmessage());
 }
 catch (classnotfoundexception localclassnotfoundexception)
 {
  throw new noclassdeffounderror(localclassnotfoundexception.getmessage());
 }
 }
}

仔細觀察可以看出生成的動態(tài)代理類有以下特點:

1.繼承自 java.lang.reflect.proxy,實現(xiàn)了 rechargable,vehicle 這兩個electriccar實現(xiàn)的接口;
2.類中的所有方法都是final 的;
3.所有的方法功能的實現(xiàn)都統(tǒng)一調(diào)用了invocationhandler的invoke()方法。

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

cglib 生成動態(tài)代理類的機制----通過類繼承:

jdk中提供的生成動態(tài)代理類的機制有個鮮明的特點是: 某個類必須有實現(xiàn)的接口,而生成的代理類也只能代理某個類接口定義的方法,比如:如果上面例子的electriccar實現(xiàn)了繼承自兩個接口的方法外,另外實現(xiàn)了方法bee() ,則在產(chǎn)生的動態(tài)代理類中不會有這個方法了!更極端的情況是:如果某個類沒有實現(xiàn)接口,那么這個類就不能同jdk產(chǎn)生動態(tài)代理了!

幸好我們有cglib。“cglib(code generation library),是一個強大的,高性能,高質(zhì)量的code生成類庫,它可以在運行期擴展java類與實現(xiàn)java接口。”
cglib 創(chuàng)建某個類a的動態(tài)代理類的模式是:
1.   查找a上的所有非final 的public類型的方法定義;
2.   將這些方法的定義轉(zhuǎn)換成字節(jié)碼;
3.   將組成的字節(jié)碼轉(zhuǎn)換成相應(yīng)的代理的class對象;
4.   實現(xiàn) methodinterceptor接口,用來處理 對代理類上所有方法的請求(這個接口和jdk動態(tài)代理invocationhandler的功能和角色是一樣的)
一個有趣的例子:定義一個programmer類,一個hacker類

?
1
2
3
4
5
6
7
8
9
10
11
12
package samples;
/**
 * 程序猿類
 * @author louluan
 */
public class programmer {
 
 public void code()
 {
  system.out.println("i'm a programmer,just coding.....");
 }
}

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package samples;
 
import java.lang.reflect.method;
 
import net.sf.cglib.proxy.methodinterceptor;
import net.sf.cglib.proxy.methodproxy;
/*
 * 實現(xiàn)了方法攔截器接口
 */
public class hacker implements methodinterceptor {
 @override
 public object intercept(object obj, method method, object[] args,
   methodproxy proxy) throws throwable {
  system.out.println("**** i am a hacker,let's see what the poor programmer is doing now...");
  proxy.invokesuper(obj, args);
  system.out.println("**** oh,what a poor programmer.....");
  return null;
 }
 
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package samples;
 
import net.sf.cglib.proxy.enhancer;
 
public class test {
 
 public static void main(string[] args) {
  programmer progammer = new programmer();
   
  hacker hacker = new hacker();
  //cglib 中加強器,用來創(chuàng)建動態(tài)代理
  enhancer enhancer = new enhancer();
     //設(shè)置要創(chuàng)建動態(tài)代理的類
  enhancer.setsuperclass(progammer.getclass());
    // 設(shè)置回調(diào),這里相當于是對于代理類上所有方法的調(diào)用,都會調(diào)用callback,而callback則需要實行intercept()方法進行攔截
    enhancer.setcallback(hacker);
    programmer proxy =(programmer)enhancer.create();
    proxy.code();
   
 }
}

程序執(zhí)行結(jié)果:

Java動態(tài)代理機制詳解_動力節(jié)點Java學(xué)院整理

讓我們看看通過cglib生成的class文件內(nèi)容:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package samples;
 
import java.lang.reflect.method;
import net.sf.cglib.core.reflectutils;
import net.sf.cglib.core.signature;
import net.sf.cglib.proxy.callback;
import net.sf.cglib.proxy.factory;
import net.sf.cglib.proxy.methodinterceptor;
import net.sf.cglib.proxy.methodproxy;
 
public class programmer
enhancerbycglib
fa7aa2cd extends programmer
 implements factory
{
 //......省略
 private methodinterceptor cglib$callback_0; // enchaner傳入的methodinterceptor
 // ....省略
 public final void code()
 {
 methodinterceptor tmp4_1 = this.cglib$callback_0;
 if (tmp4_1 == null)
 {
  tmp4_1;
  cglib$bind_callbacks(this);//若callback 不為空,則調(diào)用methodinterceptor 的intercept()方法
 }
 if (this.cglib$callback_0 != null)
  return;
  //如果沒有設(shè)置callback回調(diào)函數(shù),則默認執(zhí)行父類的方法
  super.code();
 }
 //....后續(xù)省略
}

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产三级自拍视频 | 2020国产精品视频免费 | 深夜在线 | 久久久久久久久a免费 | 日韩日韩日韩手机看片自拍 | 欧美折磨另类系列sm | 国产一区二区三区免费在线视频 | 国产3p绿奴在线视频 | 校花被拖到野外伦小说 | 亚洲成人黄色 | 水多多凹凸福利视频导航 | 视频一区二区国产 | 欧美一级高清片免费一级 | 日韩国产欧美一区二区三区 | 国产自在自线午夜精品之la | 玩50岁四川熟女大白屁股直播 | 精品国产日韩一区三区 | 精新精新国产自在现 | 国产综合视频在线 | 日本福利片国产午夜久久 | 99精品热线在线观看免费视频 | 亚洲欧美久久久久久久久久爽网站 | 日本一区二区免费在线 | 欧美x×x | ai换脸杨颖啪啪免费网站 | 69人成网站色www | 免费一级特黄特色大片在线观看 | 91青青国产在线观看免费 | 禁忌4中文| 91精品综合久久久久久五月天 | 色欧美亚洲 | ai换脸杨颖啪啪免费网站 | 免费深夜福利 | 青草视频网站 | 久久无码人妻AV精品一区 | 色婷婷婷丁香亚洲综合不卡 | 成人123 | 手机在线观看精品国产片 | 亚洲福利电影一区二区? | 美女被躁了在线观看视频 | 火影小南被爆羞羞网站 |