java的動(dòng)態(tài)代理詳解(javassist,cglib)

俗話說:Coder不知?jiǎng)討B(tài)代理瓤逼,走在路上沒人理A鳌!霸旗!
所以本文嘗試說明白java代理模式贷帮,代理中的靜態(tài)代理和動(dòng)態(tài)代理,java的動(dòng)態(tài)代理如何寫诱告,動(dòng)態(tài)代理的底層原理撵枢,spring aop中使用的cglib如何實(shí)現(xiàn),以及底層原理,cglib和jdk的動(dòng)態(tài)代理的區(qū)別锄禽,javassist的使用方法及底層實(shí)現(xiàn)原理

目錄結(jié)構(gòu)
1潜必、靜態(tài)代理
2、動(dòng)態(tài)代理
2.1沟绪、jdk的動(dòng)態(tài)代理
2.2刮便、cglib
2.3、javassist

1绽慈、靜態(tài)代理

為了便于理解動(dòng)態(tài)理解恨旱,將常見的硬編碼代理模式成為靜態(tài)代理
代理模式的定義是給某一個(gè)對象提供一個(gè)代理對象,并由代理對象控制對原對象的引用坝疼。類似于實(shí)際生活中的中介搜贤,想必都知道,這里不贅述钝凶。
代理模式的結(jié)構(gòu)圖如下:


代理模式

如圖仪芒,我們使用JavaProgramer類實(shí)現(xiàn)Programer接口,ProxyJavaProgramer類也實(shí)現(xiàn)Programer接口耕陷,同時(shí)調(diào)用JavaProgramer類掂名,對其code進(jìn)行封裝和改寫。
具體代碼如下:

//Programer 接口
public interface Programer {
  void coding();
}
//JavaProgramer 類
public class JavaProgramer implements Programer {

  public void coding() {
    System.out.println("java");
  }
}
//ProxyJavaProgramer 代理類
public class ProxyJavaProgramer implements Programer{

  public void coding() {
    JavaProgramer javaProgramer = new JavaProgramer();
    System.out.println("proxy start...");
    javaProgramer.coding();
    System.out.println("proxy end...");
  }
}
//測試類
public class Main {
  public static void main(String[] args) {
    Programer programer = new ProxyJavaProgramer();
    programer.coding();
  }
}
輸出:
proxy start...
java
proxy end...

代理模式的作用:
1哟沫、隔離具體實(shí)現(xiàn)類饺蔑,用戶直接使用的是代理類,不需要知道具體實(shí)現(xiàn)類的實(shí)現(xiàn)細(xì)節(jié)
2嗜诀、通過代理類對具體實(shí)現(xiàn)類進(jìn)行增強(qiáng)猾警,增加一些自定義功能

2、動(dòng)態(tài)代理

上面講述了靜態(tài)代理隆敢,靜態(tài)代理的缺點(diǎn)也是很明顯的发皿,1是需要程序員硬編碼,2是如果一個(gè)接口有10個(gè)實(shí)現(xiàn)類拂蝎,使用代理模式則需要編寫10個(gè)代理模式穴墅,類數(shù)量劇增。動(dòng)態(tài)代理則更加靈活一些温自,通過底層的實(shí)現(xiàn)玄货,程序員只需要將需要代理的對象傳進(jìn)來,即可實(shí)現(xiàn)對應(yīng)的代理類捣作。將代理類的生成交給程序誉结。

java中動(dòng)態(tài)代理的實(shí)現(xiàn)方式有多種鹅士,這里我們只介紹三種券躁。
分別是JDK自帶動(dòng)態(tài)代理,開源項(xiàng)目cglib和開源項(xiàng)目javassist。

下面分別講述這三種如何實(shí)現(xiàn)動(dòng)態(tài)代理及底層實(shí)現(xiàn)的原理

2.1JDK自帶動(dòng)態(tài)代理

java的自帶動(dòng)態(tài)代理實(shí)現(xiàn)的基本思想是:1:顯示Invokhandler接口也拜,2:使用Proxy類生成代理類對象以舒,3:利用反射執(zhí)行代理類的指定方法
直接上代碼,一看就明白

//IProgramer接口
public interface IProgramer {
  void coding();
}
//Programer實(shí)現(xiàn)類
public class Programer implements IProgramer{
  
  public void coding() {
    System.out.println("coding");
  }
}
//統(tǒng)一的代理類(傳入需要代理的類實(shí)例慢哈,即可)
public class JdkProxy implements InvocationHandler {
  //代理對象
  private Object target;
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("start");
    method.invoke(target,args);
    System.out.println("start");
    return null;
  }
  public JdkProxy(Object target) {
    this.target = target;
  }
}
//測試類
public class JdkProxyMain {
  public static void main(String[] args) {
    Programer programer = new Programer();
    JdkProxy jdkProxy = new JdkProxy(programer);
    IProgramer proxyProgramer =
        (IProgramer) Proxy.newProxyInstance(
            programer.getClass().getClassLoader(),
            programer.getClass().getInterfaces(),
            jdkProxy);
    proxyProgramer.coding();
  }
}
輸出結(jié)果:
start
coding
start

根據(jù)代碼蔓钟,大家肯定很清楚動(dòng)態(tài)代理是怎么回事了,下面先不著急了解底層實(shí)現(xiàn)的邏輯卵贱。我們緊接著看一下其他兩種實(shí)現(xiàn)動(dòng)態(tài)代理的方式:cglib和javassist

2.2cglib

cglib是一個(gè)開源的項(xiàng)目滥沫,使用cglib也可以實(shí)現(xiàn)動(dòng)態(tài)代理。具體怎么實(shí)現(xiàn)键俱,cglib的實(shí)現(xiàn)思路是兰绣,1:導(dǎo)入cglib依賴包,2:代理類實(shí)現(xiàn)MethodInterceptor编振,3:使用Enhance類得到動(dòng)態(tài)代理的實(shí)例缀辩,并執(zhí)行指定方法。
具體實(shí)現(xiàn)直接上代碼踪央,一看就懂

//被代理類
public class Programer {
  void codeing(){
    System.out.println("i am coding");
  }
}
//代理類
public class Hacker implements MethodInterceptor {
  public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
      throws Throwable {
    System.out.println("begin");
    methodProxy.invokeSuper(o,objects);
    System.out.println("end");
    return null;
  }
}
//測試類
public class CglibMain {

  public static void main(String[] args) {
    Programer programer = new Programer();
    Hacker hacker = new Hacker();
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(programer.getClass());
    enhancer.setCallback(hacker);
    Programer programerProxy = (Programer) enhancer.create();
    programerProxy.codeing();
  }
}
輸出結(jié)果:
begin
i am coding
end

使用cglib的實(shí)現(xiàn)動(dòng)態(tài)代理的過程更為簡單臀玄。那么cglib和JDK動(dòng)態(tài)代理有什么區(qū)別呢?區(qū)別主要有:

JDK動(dòng)態(tài)代理和CGLIB字節(jié)碼生成的區(qū)別畅蹂?
1健无、JDK動(dòng)態(tài)代理只能對實(shí)現(xiàn)了接口(注意必須有接口)的類生成代理,而不能針對類
2魁莉、CGLIB是針對類實(shí)現(xiàn)代理睬涧,主要是對指定的類生成一個(gè)子類,覆蓋其中的方法旗唁。因?yàn)槭抢^承畦浓,所以該類或方法最好不要聲明成final
3、cglib效率比JDK動(dòng)態(tài)代理要差一點(diǎn)點(diǎn)检疫,具體原因后面分析底層的實(shí)現(xiàn)的時(shí)候講解
在Spring aop中使用了JDK動(dòng)態(tài)代理和cglib讶请,使用情況是,如果實(shí)現(xiàn)了接口則使用JDK動(dòng)態(tài)代理屎媳,如果沒有實(shí)現(xiàn)接口則使用cglib夺溢。

2.3javassist

javassist是一個(gè)字節(jié)碼指令庫,利用javassist可以修改.class文件的結(jié)構(gòu)烛谊,以實(shí)現(xiàn)對類的修改和創(chuàng)建
下面直接上代碼风响,看下javassist是符合修改方法和創(chuàng)建方法的

//被修改的類
public class Programer {
  public void coding(){
    System.out.println("coding");
  }
}
//修改后的類
public class JavassistProgramer {
    /**
   * 使用javassist修改coding方法
   */
  public void updateCoding() throws Exception {
    ClassPool pool = ClassPool.getDefault();
    CtClass cc = pool.get("com.chanyi.ssist.Programer");
    CtMethod updateCoding = cc.getDeclaredMethod("coding");
    updateCoding.insertBefore("System.out.println(\"start\");");
    updateCoding.insertAfter("System.out.println(\"end\");");
    Object proxyClass = cc.toClass().newInstance();
    Method execute = proxyClass.getClass().getMethod("coding");
    execute.invoke(proxyClass);
  }

  /**
   * 使用javassist新增一個(gè)方法
   */
  public void newMethod()throws Exception {
    ClassPool pool = ClassPool.getDefault();
    CtClass cc = pool.get("com.chanyi.ssist.Programer");
    CtMethod ctMethod = new CtMethod(CtClass.voidType, "newMethod", new CtClass[]{}, cc);
    ctMethod.setModifiers(Modifier.PUBLIC);
    ctMethod.setBody("{System.out.println(\"i am new method \");}");
    cc.addMethod(ctMethod);
    Object proxyClass = cc.toClass().newInstance();
    Method personFlyMethod = proxyClass.getClass().getMethod("newMethod");
    personFlyMethod.invoke(proxyClass);
  }
}
//測試類
public class JavassistMain {

  public static void main(String[] args) {
    JavassistProgramer javassistProgramer = new JavassistProgramer();
    try {
      javassistProgramer.updateCoding();
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
  }
}
輸出結(jié)果:
start
coding
end

以上介紹了三種動(dòng)態(tài)修改.class字節(jié)碼,從而達(dá)到修改方法實(shí)現(xiàn)的方法丹禀。實(shí)際上修改.class字節(jié)碼的方法還有很多状勤。下面貼一張網(wǎng)上結(jié)構(gòu)圖鞋怀,一看就懂


結(jié)構(gòu)圖

很明顯這些方式的底層實(shí)現(xiàn)都是利用自己維護(hù)的指令類,修改和創(chuàng)建.class文件持搜。
如果有想深入了解的可以學(xué)習(xí)一下ASM密似。這里不再贅述

參數(shù)資料:
1、https://www.cnblogs.com/rickiyang/p/11336268.html
2葫盼、https://blog.csdn.net/chenchaofuck1/article/details/51727605

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末残腌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子贫导,更是在濱河造成了極大的恐慌抛猫,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孩灯,死亡現(xiàn)場離奇詭異邑滨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)钱反,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門掖看,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人面哥,你說我怎么就攤上這事哎壳。” “怎么了尚卫?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵归榕,是天一觀的道長。 經(jīng)常有香客問我吱涉,道長刹泄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任怎爵,我火速辦了婚禮特石,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鳖链。我一直安慰自己姆蘸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布芙委。 她就那樣靜靜地躺著逞敷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪灌侣。 梳的紋絲不亂的頭發(fā)上推捐,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天,我揣著相機(jī)與錄音侧啼,去河邊找鬼牛柒。 笑死愕秫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的焰络。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼符喝,長吁一口氣:“原來是場噩夢啊……” “哼闪彼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起协饲,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤畏腕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后茉稠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體描馅,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年而线,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铭污。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,626評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡膀篮,死狀恐怖嘹狞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情誓竿,我是刑警寧澤磅网,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站筷屡,受9級特大地震影響涧偷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜毙死,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一燎潮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扼倘,春花似錦跟啤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至袄简,卻和暖如春腥放,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绿语。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工秃症, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留候址,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓种柑,卻偏偏與公主長得像岗仑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子聚请,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評論 2 348