Java動態(tài)編程初探——Javassist

Java動態(tài)編程初探——Javassist

最近需要通過配置生成代碼,減少重復編碼和維護成本抒寂。用到了一些動態(tài)的特性结啼,和大家分享下心得嘶窄。

我們常用到的動態(tài)特性主要是反射恨狈,在運行時查找對象屬性、方法敞映,修改作用域沸伏,通過方法名稱調(diào)用方法等。在線的應用不會頻繁使用反射动分,因為反射的性能開銷較大毅糟。其實還有一種和反射一樣強大的特性,但是開銷卻很低澜公,它就是Javassit姆另。

Javassit其實就是一個二方包,提供了運行時操作Java字節(jié)碼的方法坟乾。大家都知道迹辐,Java代碼編譯完會生成.class文件,就是一堆字節(jié)碼甚侣。JVM(準確說是JIT)會解釋執(zhí)行這些字節(jié)碼(轉(zhuǎn)換為機器碼并執(zhí)行)明吩,由于字節(jié)碼的解釋執(zhí)行是在運行時進行的,那我們能否手工編寫字節(jié)碼殷费,再由JVM執(zhí)行呢印荔?答案是肯定的,而Javassist就提供了一些方便的方法详羡,讓我們通過這些方法生成字節(jié)碼仍律。

類似字節(jié)碼操作方法還有ASM。幾種動態(tài)編程方法相比較实柠,在性能上Javassist高于反射水泉,但低于ASM,因為Javassist增加了一層抽象窒盐。在實現(xiàn)成本上Javassist和反射都很低草则,而ASM由于直接操作字節(jié)碼,相比Javassist源碼級別的api實現(xiàn)成本高很多登钥。幾個方法有自己的應用場景畔师,比如Kryo使用的是ASM,追求性能的最大化牧牢。而NBeanCopyUtil采用的是Javassist看锉,在對象拷貝的性能上也已經(jīng)明顯高于其他的庫姿锭,并保持高易用性。實際項目中推薦先用Javassist實現(xiàn)原型伯铣,若在性能測試中發(fā)現(xiàn)Javassist成為了性能瓶頸呻此,再考慮使用其他字節(jié)碼操作方法做優(yōu)化

Javassist的使用很簡單腔寡,首先獲取到class定義的容器ClassPool焚鲜,通過它獲取已經(jīng)編譯好的類(Compile time class),并給這個類設置一個父類放前,而writeFile講這個類的定義從新寫到磁盤忿磅,以便后面使用。

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("test.Rectangle");
cc.setSuperclass(pool.get("test.Point"));
cc.writeFile(); 

由CtClass可以方便的獲取字節(jié)碼和加載字節(jié)碼:

 byte[] b = cc.toBytecode();
 Class clazz = cc.toClass(); 

如果需要定義一個新類凭语,只需要

 ClassPool pool = ClassPool.getDefault();
 CtClass cc = pool.makeClass("Point"); 

同樣的還可以通過CtMethod和CtField構(gòu)造方法和成員甚至Annotation葱她。

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("foo");
CtMethod mthd = CtNewMethod.make("public Integer 
getInteger() { return null; }", cc);
cc.addMethod(mthd);

CtField f = new CtField(CtClass.intType, "i", cc);
point.addField(f);

clazz = cc.toClass(); Object instance = class.newInstance();

Javassist不僅可以生成類、變量和方法似扔,還可以操作現(xiàn)有的方法吨些,這在AOP上非常有用,比如做方法調(diào)用的埋點

// Point.java
class Point {
int x, y;
void move(int dx, int dy) { x += dx; y += dy; }
}

// 對已有代碼每次move執(zhí)行時做埋點
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
CtMethod m = cc.getDeclaredMethod("move");
m.insertBefore("{ System.out.println($1); System.out.println($2); }");
cc.writeFile();

其中1和2表示調(diào)用棧中的第一和第二個參數(shù)炒辉,寫到磁盤后的class定義類似:

class Point {
int x, y;
void move(int dx, int dy) {
    { System.out.println(dx); System.out.println(dy); }
    x += dx; y += dy;
}
}

在使用Javassist時遇到過一些問題豪墅。

1 因為tomcat和jboss使用的是獨立的classloader,而Javassist是通過默認的classloader加載類黔寇,因此直接對tomcat context中定義的類做toClass會拋出ClassCastException異常偶器,可以用tomcat的classloader加載字節(jié)碼。

CtClass cc = ...;
Class c = cc.toClass(bean.getClass().getClassLoader());

2 發(fā)現(xiàn)在簡單的測試中可以load的類啡氢,在tomcat中無法load状囱。這是因為,ClassPool.getDefault()查找的路徑和底層的JVM路徑倘是。而tomcat中定義了多個classloader亭枷,因此額外的class路徑需要注冊到ClassPool中。

pool.insertClassPath(new ClassClassPath(this.getClass()));

3 我想在運行時修改類的一個方法搀崭,但是JVM是不允許動態(tài)的reload類定義的叨粘。一旦classloader加載了一個class,在運行時就不能重新加載這個class的另一個版本瘤睹,調(diào)用toClass()會拋LinkageError升敲。因此需要繞過這種方式定義全新的class。而toClass()其實是當前thread所在的classloader加載class轰传。

4 Javassist生成的字節(jié)碼由于沒有class聲明驴党,字節(jié)碼創(chuàng)建變量及方法調(diào)用都需要通過反射。這點在在線的應用上的性能損失是不能接受的获茬,受到NBeanCopyUtil實現(xiàn)的啟發(fā)港庄,可以定義一個Interface倔既,Javassist的字節(jié)碼實現(xiàn)這個Interface,而調(diào)用方通過這個接口調(diào)用字節(jié)碼鹏氧,而不是反射渤涌,這樣避免了反射調(diào)用的開銷。還有一點字節(jié)碼new一個變量也是通過反射把还,因此通過代理的方法实蓬,將每個pv都需要new的字節(jié)碼對象改為每次new一個代理對象,代理到常駐內(nèi)存的字節(jié)碼對象中吊履,這樣避免了每次反射的開銷安皱。

參考資料:

http://asm.ow2.org/

http://notatube.blogspot.com/2010/11/project-lombok-trick-explained.html

http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/tutorial/tutorial.html

http://www.ibm.com/developerworks/cn/java/coretech/java-dynamic.htm

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市艇炎,隨后出現(xiàn)的幾起案子练俐,更是在濱河造成了極大的恐慌,老刑警劉巖冕臭,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異燕锥,居然都是意外死亡辜贵,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門归形,熙熙樓的掌柜王于貴愁眉苦臉地迎上來托慨,“玉大人,你說我怎么就攤上這事暇榴『窨茫” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵蔼紧,是天一觀的道長婆硬。 經(jīng)常有香客問我,道長奸例,這世上最難降的妖魔是什么彬犯? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮查吊,結(jié)果婚禮上谐区,老公的妹妹穿的比我還像新娘。我一直安慰自己逻卖,他們只是感情好宋列,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著评也,像睡著了一般炼杖。 火紅的嫁衣襯著肌膚如雪灭返。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天嘹叫,我揣著相機與錄音婆殿,去河邊找鬼。 笑死罩扇,一個胖子當著我的面吹牛婆芦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播喂饥,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼消约,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了员帮?” 一聲冷哼從身側(cè)響起或粮,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捞高,沒想到半個月后氯材,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡硝岗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年氢哮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片型檀。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡冗尤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胀溺,到底是詐尸還是另有隱情裂七,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布仓坞,位于F島的核電站背零,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扯躺。R本人自食惡果不足惜捉兴,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望录语。 院中可真熱鬧倍啥,春花似錦、人聲如沸澎埠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蒲稳。三九已至氮趋,卻和暖如春伍派,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背剩胁。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工诉植, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人昵观。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓晾腔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親啊犬。 傳聞我的和親對象是個殘疾皇子灼擂,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容