欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Javassist之一秒理解java動(dòng)態(tài)編程

 更新時(shí)間:2019年06月04日 09:50:15   作者:ShuSheng007  
概述Javassist是一款字節(jié)碼編輯工具,可以直接編輯和生成Java生成的字節(jié)碼,以達(dá)到對(duì).class文件進(jìn)行動(dòng)態(tài)修改的效果。

概述

什么是動(dòng)態(tài)編程?動(dòng)態(tài)編程解決什么問(wèn)題?Java中如何使用?什么原理?如何改進(jìn)?(需要我們一起探索,由于自己也是比較菜,一般深入不到這個(gè)程度)。

什么是動(dòng)態(tài)編程

動(dòng)態(tài)編程是相對(duì)于靜態(tài)編程而言的,平時(shí)我們討論比較多的就是靜態(tài)編程語(yǔ)言,例如Java,與動(dòng)態(tài)編程語(yǔ)言,例如JavaScript。那二者有什么明顯的區(qū)別呢?簡(jiǎn)單的說(shuō)就是在靜態(tài)編程中,類(lèi)型檢查是在編譯時(shí)完成的,而動(dòng)態(tài)編程中類(lèi)型檢查是在運(yùn)行時(shí)完成的。所謂動(dòng)態(tài)編程就是繞過(guò)編譯過(guò)程在運(yùn)行時(shí)進(jìn)行操作的技術(shù),在Java中有如下幾種方式:

反射

這個(gè)搞Java的應(yīng)該比較熟悉,原理也就是通過(guò)在運(yùn)行時(shí)獲得類(lèi)型信息然后做相應(yīng)的操作。

動(dòng)態(tài)編譯

動(dòng)態(tài)編譯是從Java 6開(kāi)始支持的,主要是通過(guò)一個(gè)JavaCompiler接口來(lái)完成的。通過(guò)這種方式我們可以直接編譯一個(gè)已經(jīng)存在的java文件,也可以在內(nèi)存中動(dòng)態(tài)生成Java代碼,動(dòng)態(tài)編譯執(zhí)行。

調(diào)用JavaScript引擎

Java 6加入了對(duì)Script(JSR223)的支持。這是一個(gè)腳本框架,提供了讓腳本語(yǔ)言來(lái)訪問(wèn)Java內(nèi)部的方法。你可以在運(yùn)行的時(shí)候找到腳本引擎,然后調(diào)用這個(gè)引擎去執(zhí)行腳本。這個(gè)腳本API允許你為腳本語(yǔ)言提供Java支持。

動(dòng)態(tài)生成字節(jié)碼

這種技術(shù)通過(guò)操作Java字節(jié)碼的方式在JVM中生成新類(lèi)或者對(duì)已經(jīng)加載的類(lèi)動(dòng)態(tài)添加元素。

動(dòng)態(tài)編程解決什么問(wèn)題

在靜態(tài)語(yǔ)言中引入動(dòng)態(tài)特性,主要是為了解決一些使用場(chǎng)景的痛點(diǎn)。其實(shí)完全使用靜態(tài)編程也辦的到,只是付出的代價(jià)比較高,沒(méi)有動(dòng)態(tài)編程來(lái)的優(yōu)雅。例如依賴(lài)注入框架Spring使用了反射,而Dagger2 卻使用了代碼生成的方式(APT)。

例如

1: 在那些依賴(lài)關(guān)系需要?jiǎng)討B(tài)確認(rèn)的場(chǎng)景:
2: 需要在運(yùn)行時(shí)動(dòng)態(tài)插入代碼的場(chǎng)景,比如動(dòng)態(tài)代理的實(shí)現(xiàn)。
3: 通過(guò)配置文件來(lái)實(shí)現(xiàn)相關(guān)功能的場(chǎng)景

Java中如何使用

此處我們主要說(shuō)一下通過(guò)動(dòng)態(tài)生成字節(jié)碼的方式,其他方式可以自行查找資料。

操作java字節(jié)碼的工具有兩個(gè)比較流行,一個(gè)是ASM,一個(gè)是Javassit 。

ASM :直接操作字節(jié)碼指令,執(zhí)行效率高,要是使用者掌握J(rèn)ava類(lèi)字節(jié)碼文件格式及指令,對(duì)使用者的要求比較高。

Javassit 提供了更高級(jí)的API,執(zhí)行效率相對(duì)較差,但無(wú)需掌握字節(jié)碼指令的知識(shí),對(duì)使用者要求較低。

應(yīng)用層面來(lái)講一般使用建議優(yōu)先選擇Javassit,如果后續(xù)發(fā)現(xiàn)Javassit 成為了整個(gè)應(yīng)用的效率瓶頸的話可以再考慮ASM.當(dāng)然如果開(kāi)發(fā)的是一個(gè)基礎(chǔ)類(lèi)庫(kù),或者基礎(chǔ)平臺(tái),還是直接使用ASM吧,相信從事這方面工作的開(kāi)發(fā)者能力應(yīng)該比較高。

上一張國(guó)外博客的圖,展示處理Java字節(jié)碼的工具的關(guān)系。

接下來(lái)介紹如何使用Javassit來(lái)操作字節(jié)碼

Javassit使用方法

Javassist是一個(gè)開(kāi)源的分析、編輯和創(chuàng)建Java字節(jié)碼的類(lèi)庫(kù)。是由東京工業(yè)大學(xué)的數(shù)學(xué)和計(jì)算機(jī)科學(xué)系的 Shigeru Chiba (千葉 滋)所創(chuàng)建的。

它已加入了開(kāi)放源代碼JBoss 應(yīng)用服務(wù)器項(xiàng)目,通過(guò)使用Javassist對(duì)字節(jié)碼操作為JBoss實(shí)現(xiàn)動(dòng)態(tài)AOP框架。

javassist是jboss的一個(gè)子項(xiàng)目,其主要的優(yōu)點(diǎn),在于簡(jiǎn)單,而且快速。直接使用java編碼的形式,而不需要了解虛擬機(jī)指令,就能動(dòng)態(tài)改變類(lèi)的結(jié)構(gòu),或者動(dòng)態(tài)生成類(lèi)。

Javassist中最為重要的是ClassPool,CtClass ,CtMethod 以及 CtField這幾個(gè)類(lèi)。

ClassPool:一個(gè)基于HashMap實(shí)現(xiàn)的CtClass對(duì)象容器,其中鍵是類(lèi)名稱(chēng),值是表示該類(lèi)的CtClass對(duì)象。默認(rèn)的ClassPool使用與底層JVM相同的類(lèi)路徑,因此在某些情況下,可能需要向ClassPool添加類(lèi)路徑或類(lèi)字節(jié)。

CtClass:表示一個(gè)類(lèi),這些CtClass對(duì)象可以從ClassPool獲得。

CtMethods:表示類(lèi)中的方法。

CtFields :表示類(lèi)中的字段。

動(dòng)態(tài)生成一個(gè)類(lèi)

下面的代碼會(huì)生成一個(gè)實(shí)現(xiàn)了Cloneable接口的類(lèi)GenerateClass

public void DynGenerateClass() {
ClassPool pool = ClassPool.getDefault();
CtClass ct = pool.makeClass("top.ss007.GenerateClass");//創(chuàng)建類(lèi)
ct.setInterfaces(new CtClass[]{pool.makeInterface("java.lang.Cloneable")});//讓類(lèi)實(shí)現(xiàn)Cloneable接口
try {
CtField f= new CtField(CtClass.intType,"id",ct);//獲得一個(gè)類(lèi)型為int,名稱(chēng)為id的字段
f.setModifiers(AccessFlag.PUBLIC);//將字段設(shè)置為public
ct.addField(f);//將字段設(shè)置到類(lèi)上
//添加構(gòu)造函數(shù)
CtConstructor constructor=CtNewConstructor.make("public GeneratedClass(int pId){this.id=pId;}",ct);
ct.addConstructor(constructor);
//添加方法
CtMethod helloM=CtNewMethod.make("public void hello(String des){ System.out.println(des);}",ct);
ct.addMethod(helloM);
ct.writeFile();//將生成的.class文件保存到磁盤(pán)
//下面的代碼為驗(yàn)證代碼
Field[] fields = ct.toClass().getFields();
System.out.println("屬性名稱(chēng):" + fields[0].getName() + " 屬性類(lèi)型:" + fields[0].getType());
} catch (CannotCompileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NotFoundException e) {
e.printStackTrace();
}
}

上面的代碼就會(huì)動(dòng)態(tài)生成一個(gè).class文件,我們使用反編譯工具,例如Bytecode Viewer,查看生成的字節(jié)碼文件GenerateClass.class,如下圖所示。

動(dòng)態(tài)添加構(gòu)造函數(shù)及方法

有很多種方法添加構(gòu)造函數(shù),我們使用CtNewConstructor.make,他是一個(gè)的靜態(tài)方法,其中有一個(gè)重載版本比較方便,如下所示。第一個(gè)參數(shù)是source text 類(lèi)型的方法體,第二個(gè)為類(lèi)對(duì)象。

CtConstructor constructor=CtNewConstructor.make("public GeneratedClass(int pId){this.id=pId;}",ct);
ct.addConstructor(constructor); 

這段代碼執(zhí)行后會(huì)生成如下java代碼,代碼片段是使用反編譯工具JD-GUI產(chǎn)生的,可以看到構(gòu)造函數(shù)的參數(shù)名被修改成了paramInt。

public GeneratedClass(int paramInt)
{
this.id = paramInt;
}

同樣有很多種方法添加函數(shù),我們使用CtNewMethod.make這個(gè)比較簡(jiǎn)單的形式

CtMethod helloM=CtNewMethod.make("public void hello(String des){ System.out.println(des);}",ct);
ct.addMethod(helloM);

這段代碼執(zhí)行后會(huì)生成如下java代碼:

public void hello(String paramString)
{
System.out.println(paramString);
}

動(dòng)態(tài)修改方法體

動(dòng)態(tài)的修改一個(gè)方法的內(nèi)容才是我們關(guān)注的重點(diǎn),例如在AOP編程方面,我們就會(huì)用到這種技術(shù),動(dòng)態(tài)的在一個(gè)方法中插入代碼。
例如我們有下面這樣一個(gè)類(lèi)

public class Point {
private int x;
private int y;
public Point(){}
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public void move(int dx, int dy) {
this.x += dx;
this.y += dy;
}
}

我們要?jiǎng)討B(tài)的在內(nèi)存中在move()方法體的前后插入一些代碼

public void modifyMethod()
{
ClassPool pool=ClassPool.getDefault();
try {
CtClass ct=pool.getCtClass("top.ss007.Point");
CtMethod m=ct.getDeclaredMethod("move");
m.insertBefore("{ System.out.print(\"dx:\"+$1); System.out.println(\"dy:\"+$2);}");
m.insertAfter("{System.out.println(this.x); System.out.println(this.y);}");
ct.writeFile();
//通過(guò)反射調(diào)用方法,查看結(jié)果
Class pc=ct.toClass();
Method move= pc.getMethod("move",new Class[]{int.class,int.class});
Constructor<?> con=pc.getConstructor(new Class[]{int.class,int.class});
move.invoke(con.newInstance(1,2),1,2);
}
...
}

使用反編譯工具查看修改后的move方法結(jié)果:

public void move(int dx, int dy) {
System.out.print("dx:" + dx);System.out.println("dy:" + dy);
this.x += dx;
this.y += dy;
Object localObject = null;//方法返回值
System.out.println(this.x);System.out.println(this.y);
}

可以看到,在生成的字節(jié)碼文件中確實(shí)增加了相應(yīng)的代碼。
函數(shù)輸出結(jié)果為:

dx:1dy:2
2
4

Javassit 還有許多功能,例如在方法中調(diào)用方法,異常捕捉,類(lèi)型強(qiáng)制轉(zhuǎn)換,注解相關(guān)操作等,而且其還提供了字節(jié)碼層面的API(Bytecode level API)。

什么原理

反射:由于Java執(zhí)行過(guò)程中是將類(lèi)型載入虛擬機(jī)中的,在運(yùn)行時(shí)我們就可以動(dòng)態(tài)獲取到所有類(lèi)型的信息。只能獲取卻不能修類(lèi)型信息。

動(dòng)態(tài)編譯與動(dòng)態(tài)生成字節(jié)碼:這兩種方法比較相似,原理也都是利用了Java的設(shè)計(jì)原理,存在一個(gè)虛擬機(jī)執(zhí)行字節(jié)碼,這就使我們?cè)诖颂幱辛烁淖冏止?jié)碼的操作空間。

總結(jié)

有關(guān)動(dòng)態(tài)編程的知識(shí)在平時(shí)的應(yīng)用層使用不是特別多,多是用在構(gòu)建框架。例如Spring框架使用反射來(lái)構(gòu)建,而用于AOP編程的動(dòng)態(tài)代理則多是采用生成字節(jié)碼的方式,例如JBoss,Spring中的AOP部分。了解這部分知識(shí)可以在日后遇到相關(guān)問(wèn)題時(shí)比別人多一條思考的思路也是好的,做一個(gè)思路開(kāi)闊的Developer。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論