字節(jié)碼增強(qiáng)技術(shù),不止有 Java Proxy、 Cglib 和 Javassist 還有 Byte Buddy
提到字節(jié)碼增強(qiáng)技術(shù),相信用過(guò) Spring 的小伙伴都會(huì)知道 Java Proxy 和 Cglib。
畢竟面試準(zhǔn)備的八股文中說(shuō)過(guò),Spring 的動(dòng)態(tài)代理有兩種實(shí)現(xiàn)方式,在有接口存在的時(shí)候使用 Java Proxy,當(dāng)沒(méi)有接口的時(shí)候使用的是 Cglib。
這兩種方式的區(qū)別不在本文的討論范圍之內(nèi),今天想給大家介紹了是另一個(gè)字節(jié)碼增強(qiáng)技術(shù) Byte Buddy。
Byte Buddy
根據(jù) Byte Buddy 官網(wǎng)所說(shuō),Byte Buddy 是一個(gè)代碼生成和操作庫(kù),用于在 Java 應(yīng)用程序運(yùn)行時(shí)創(chuàng)建和修改 Java 類(lèi),而無(wú)需編譯器的幫助。
Byte Buddy 提供一套簡(jiǎn)單易用的 API,可以很方便的使用 Java 流式編程的形式來(lái)動(dòng)態(tài)創(chuàng)建類(lèi)或者創(chuàng)建接口的實(shí)現(xiàn)類(lèi),這一點(diǎn)跟 Java Proxy 和 Cglib 不一樣。
使用 Byte Buddy 的方式也非常簡(jiǎn)單,只要直接引入 Maven 依賴(lài)即可,沒(méi)有其他繁瑣的依賴(lài)??偟膩?lái)說(shuō),使用 Byte Buddy 有下面的優(yōu)勢(shì):
- 無(wú)需理解字節(jié)碼格式,簡(jiǎn)單易用的 API 能很容易操作字節(jié)碼;
- 支持 Java 任何版本,庫(kù)輕量,僅取決于 Java 字節(jié)代碼解析器庫(kù) ASM 的訪問(wèn)者 API,它本身不需要任何其他依賴(lài)項(xiàng)。
- 比起 JDK 動(dòng)態(tài)代理、cglib、Javassist,Byte Buddy 在性能上具有優(yōu)勢(shì)。
圖片
這一份測(cè)試報(bào)告是官網(wǎng)提供的,表中的每一行分別為,類(lèi)的創(chuàng)建、接口實(shí)現(xiàn)、方法調(diào)用、類(lèi)型擴(kuò)展、父類(lèi)方法調(diào)用的性能結(jié)果。
從性能報(bào)告中可以看出,Byte Buddy 在一些場(chǎng)景是有優(yōu)勢(shì)的,但是在有些場(chǎng)景也不見(jiàn)得特別有優(yōu)勢(shì),不過(guò)整體來(lái)看還是不錯(cuò)的。
測(cè)試
說(shuō)了那么多,下面給大家演示一下,如果使用 Byte Buddy,首先我們需要引入 Maven 依賴(lài),我這里用的版本是 1.14.6,也可以使用其他版本。
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.6</version>
</dependency>
創(chuàng)建一個(gè)類(lèi),并覆蓋 toString
public static void test1() {
try {
Class<?> dynamicType = new ByteBuddy().
subclass(Object.class)
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value("Hello World!"))
.make()
.load(ByteBuddyDemo.class.getClassLoader())
.getLoaded();
System.out.println(dynamicType.newInstance().toString());
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
public static void test2() {
try {
DynamicType.Unloaded<Object> unloaded = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value("Hello World!"))
.make();
DynamicType.Loaded<Object> load = unloaded.load(ByteBuddyDemo.class.getClassLoader());
System.out.println(load.getLoaded().newInstance().toString());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
整個(gè)代碼的思路是通過(guò) Byte Buddy,構(gòu)造出一個(gè) Class 對(duì)象,然后調(diào)用 Class 對(duì)象的 newInstance() 方法,再執(zhí)行 toString() 方法。上面兩個(gè)方式的功能是一樣的,寫(xiě)出來(lái)更方便大家理解。
其中各個(gè)方法的含義如下:
subClass:表示構(gòu)造的類(lèi)是 Object 的子類(lèi);
method:表示要構(gòu)造的具體方法,類(lèi)似于過(guò)濾的功能;
intercept:表示對(duì)過(guò)濾后的方法進(jìn)行攔截;
FixedValue.value("Hello World!"):表示構(gòu)造返回一個(gè)”Hello World!“ 字符串;
make:創(chuàng)建 DynamicType.Unloaded 對(duì)象,此時(shí)這個(gè)對(duì)象被構(gòu)造出來(lái),但是還沒(méi)有被 JVM 加載,還不能使用;
load,getLoaded:加載當(dāng)前類(lèi)的構(gòu)造器,并進(jìn)行加載;
等到加載到 JVM 過(guò)后,就可以使用 newInstance().toString() 進(jìn)行調(diào)用了。
代理方法
上面的例子是創(chuàng)建一個(gè)簡(jiǎn)單的類(lèi)和方法,下面我們介紹一個(gè)代理方法的使用,這里我們有一個(gè)目標(biāo)類(lèi) Target 和一個(gè)方法 saySomething() 方法,有一個(gè)代理類(lèi) Agent,里面有一個(gè)代理方法 agentSaySomething(),如下所示:
public class Target {
public String saySomething() {
return "Hello target";
}
}
public class Agent {
public static String agentSaySomething() {
System.out.println("agentSaySomething");
return "hello agent";
}
}
public static void test4() {
try {
DynamicType.Unloaded<Target> agent = new ByteBuddy()
.subclass(Target.class)
.method(named("saySomething")
.and(isDeclaredBy(Target.class)
.and(returns(String.class))))
.intercept(MethodDelegation.to(Agent.class))
.make();
// 將 agent 字節(jié)碼寫(xiě)入文件中
outputClazz(agent.getBytes());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void outputClazz(byte[] bytes) {
FileOutputStream out = null;
try {
String pathName = ByteBuddyDemo.class.getResource("/").getPath() + "AgentTarget.class";
out = new FileOutputStream(new File(pathName));
System.out.println("類(lèi)輸出路徑:" + pathName);
out.write(bytes);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != out) try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
test4();
}
運(yùn)行過(guò)后我們可以看到生成了一個(gè) class 文件,通過(guò)查看代碼如下,可以看到是創(chuàng)建了一個(gè) Target 的子類(lèi),并且調(diào)用了 Agent 的 agentSaySomething 方法。
圖片
總結(jié)
Byte Buddy的 API 很豐富,這里只是很簡(jiǎn)單的給大家使用了幾個(gè) API,還有包括方法,字段的設(shè)定等等,感興趣的小伙伴可以繼續(xù)去學(xué)習(xí)學(xué)習(xí)。