论坛
字节码不止有 Java Proxy、 Cglib 和 Javassist 还有 ByteBuddy
引用于 程序员子悠 在 2024年2月14日, 下午3:21提到字节码增强技术,相信用过
Spring
的小伙伴都会知道Java Proxy
和Cglib
。毕竟面试准备的八股文中说过,
Spring
的动态代理有两种实现方式,在有接口存在的时候使用Java Proxy
,当没有接口的时候使用的是Cglib
。这两种方式的区别不在本文的讨论范围之内,今天想给大家介绍了是另一个字节码增强技术
Byte Buddy
。Byte Buddy
根据
Byte Buddy
官网所说,Byte Buddy
是一个代码生成和操作库,用于在Java
应用程序运行时创建和修改Java
类,而无需编译器的帮助。
Byte Buddy
提供一套简单易用的API
,可以很方便的使用Java
流式编程的形式来动态创建类或者创建接口的实现类,这一点跟Java Proxy
和Cglib
不一样。使用
Byte Buddy
的方式也非常简单,只要直接引入Maven
依赖即可,没有其他繁琐的依赖。总的来说,使用Byte Buddy
有下面的优势:
无需理解字节码格式,简单易用的 API
能很容易操作字节码;支持 Java
任何版本,库轻量,仅取决于Java
字节代码解析器库ASM
的访问者API
,它本身不需要任何其他依赖项。比起 JDK
动态代理、cglib
、Javassist
,Byte Buddy
在性能上具有优势。这一份测试报告是官网提供的,表中的每一行分别为,类的创建、接口实现、方法调用、类型扩展、父类方法调用的性能结果。
从性能报告中可以看出,
Byte Buddy
在一些场景是有优势的,但是在有些场景也不见得特别有优势,不过整体来看还是不错的。测试
说了那么多,下面给大家演示一下,如果使用
Byte Buddy
,首先我们需要引入Maven
依赖,我这里用的版本是 1.14.6,也可以使用其他版本。
<dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.14.6</version> </dependency>
创建一个类,并覆盖 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); } }
整个代码的思路是通过
Byte Buddy
,构造出一个Class
对象,然后调用Class
对象的newInstance()
方法,再执行toString()
方法。上面两个方式的功能是一样的,写出来更方便大家理解。其中各个方法的含义如下:
subClass
:表示构造的类是Object
的子类;
method
:表示要构造的具体方法,类似于过滤的功能;
intercept
:表示对过滤后的方法进行拦截;
FixedValue.value("Hello World!")
:表示构造返回一个”Hello World!“ 字符串;
make
:创建DynamicType.Unloaded
对象,此时这个对象被构造出来,但是还没有被JVM
加载,还不能使用;
load
,getLoaded
:加载当前类的构造器,并进行加载;等到加载到
JVM
过后,就可以使用newInstance().toString()
进行调用了。代理方法
上面的例子是创建一个简单的类和方法,下面我们介绍一个代理方法的使用,这里我们有一个目标类 Target 和一个方法
saySomething()
方法,有一个代理类Agent
,里面有一个代理方法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 字节码写入文件中 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("类输出路径:" + 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(); }
运行过后我们可以看到生成了一个
class
文件,通过查看代码如下,可以看到是创建了一个Target
的子类,并且调用了Agent
的agentSaySomething
方法。总结
Byte Buddy
的API
很丰富,这里只是很简单的给大家使用了几个API
,还有包括方法,字段的设定等等,感兴趣的小伙伴可以继续去学习学习。
提到字节码增强技术,相信用过 Spring
的小伙伴都会知道 Java Proxy
和 Cglib
。
毕竟面试准备的八股文中说过,Spring
的动态代理有两种实现方式,在有接口存在的时候使用 Java Proxy
,当没有接口的时候使用的是 Cglib
。
这两种方式的区别不在本文的讨论范围之内,今天想给大家介绍了是另一个字节码增强技术 Byte Buddy
。
Byte Buddy
根据 Byte Buddy
官网所说,Byte Buddy
是一个代码生成和操作库,用于在 Java
应用程序运行时创建和修改 Java
类,而无需编译器的帮助。
Byte Buddy
提供一套简单易用的 API
,可以很方便的使用 Java
流式编程的形式来动态创建类或者创建接口的实现类,这一点跟 Java Proxy
和 Cglib
不一样。
使用 Byte Buddy
的方式也非常简单,只要直接引入 Maven
依赖即可,没有其他繁琐的依赖。总的来说,使用 Byte Buddy
有下面的优势:
-
无需理解字节码格式,简单易用的 API
能很容易操作字节码; -
支持 Java
任何版本,库轻量,仅取决于Java
字节代码解析器库ASM
的访问者API
,它本身不需要任何其他依赖项。 -
比起 JDK
动态代理、cglib
、Javassist
,Byte Buddy
在性能上具有优势。
这一份测试报告是官网提供的,表中的每一行分别为,类的创建、接口实现、方法调用、类型扩展、父类方法调用的性能结果。
从性能报告中可以看出,Byte Buddy
在一些场景是有优势的,但是在有些场景也不见得特别有优势,不过整体来看还是不错的。
测试
说了那么多,下面给大家演示一下,如果使用 Byte Buddy
,首先我们需要引入 Maven
依赖,我这里用的版本是 1.14.6,也可以使用其他版本。
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.6</version>
</dependency>
创建一个类,并覆盖 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);
}
}
整个代码的思路是通过 Byte Buddy
,构造出一个 Class
对象,然后调用 Class
对象的 newInstance()
方法,再执行 toString()
方法。上面两个方式的功能是一样的,写出来更方便大家理解。
其中各个方法的含义如下:
subClass
:表示构造的类是 Object
的子类;
method
:表示要构造的具体方法,类似于过滤的功能;
intercept
:表示对过滤后的方法进行拦截;
FixedValue.value("Hello World!")
:表示构造返回一个”Hello World!“ 字符串;
make
:创建 DynamicType.Unloaded
对象,此时这个对象被构造出来,但是还没有被 JVM
加载,还不能使用;
load
,getLoaded
:加载当前类的构造器,并进行加载;
等到加载到 JVM
过后,就可以使用 newInstance().toString()
进行调用了。
代理方法
上面的例子是创建一个简单的类和方法,下面我们介绍一个代理方法的使用,这里我们有一个目标类 Target 和一个方法 saySomething()
方法,有一个代理类 Agent
,里面有一个代理方法 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 字节码写入文件中
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("类输出路径:" + 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();
}
运行过后我们可以看到生成了一个 class
文件,通过查看代码如下,可以看到是创建了一个 Target
的子类,并且调用了 Agent
的 agentSaySomething
方法。
总结
Byte Buddy
的 API
很丰富,这里只是很简单的给大家使用了几个 API
,还有包括方法,字段的设定等等,感兴趣的小伙伴可以继续去学习学习。