背景
在 Byte Buddy 的 网站 上可以找到以下文章
- Creating a class (这篇是英文)
- 类创建 (这篇是中文)
在文章中,我们可以看到很多具体的例子。其中一个例子如下
java
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
.subclass(Object.class)
.make();
我想看看 Byte Byddy 所生成的类的结构是怎样的,所以写了本文来进行记录。让我们开始探索吧。
正文
第一个例子: Object 的子类
在 类创建 里,可以找到如下的例子

我把代码复制到下方了 ⬇️
java
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
.subclass(Object.class)
.make();
如果想查看对应的 class 文件的结构,需要做些准备工作。
准备工作
在 Byte Buddy (without Dependencies) 页面,可以看到 Byte Buddy 的版本列表 ⬇️
我们就用最新的那个吧(版本号是 1.18.7)。可以在 Byte Buddy (without Dependencies) >> 1.18.7 页面下载 jar 包 ⬇️

我将它下载到本地后,文件的名称是 byte-buddy-1.18.7.jar。
为了行文的方便,本文就用 javac/java/javap 这些命令直接在命令行来进行操作了。但其实还是建一个专门的项目(例如 maven 项目)更容易处理各种问题,而且写代码也更方便。
我们把如下的代码保存到 Main.java 文件里。
java
import net.bytebuddy.ByteBuddy;
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
new ByteBuddy()
.subclass(Object.class)
.make()
.saveIn(new File("."));
}
}
编译与运行
通过执行如下命令,就可以编译 Main.java (请注意要先把 byte-buddy-1.18.7.jar 移动到当前路径下)
bash
javac -cp byte-buddy-1.18.7.jar Main.java
编译成功后,应该可以在当前目录下看到 Main.class 文件。然后再执行如下命令就可以运行 Main 类里的 main 方法。
bash
java -cp byte-buddy-1.18.7.jar:. Main
运行后没有任何输出,这是符合预期的。因为 Main 类里的 main 方法并不会在标准输出里打印任何内容。此时执行 ls 命令,会发现当前目录下多了一个 net 目录。我在当前目录下执行 tree . 命令后,看到的结果如下 ⬇️
text
.
├── byte-buddy-1.18.7.jar
├── Main.class
├── Main.java
└── net
└── bytebuddy
└── renamed
└── java
└── lang
└── Object$ByteBuddy$i8uhbcjD.class
6 directories, 4 files
(也许 Byte Buddy 在你电脑上生成的 class 文件会是另一个名字)
看来 Object$ByteBuddy$i8uhbcjD.class 就是 Byte Buddy 所生成的 class 文件了。通过执行如下命令就可以查看它的内容 (在你的电脑上,class 文件的名称可能会有差异,请注意调整)
bash
javap -v -p net.bytebuddy.renamed.java.lang.Object\$ByteBuddy\$i8uhbcjD
其输出如下 (我删掉了前三行)
text
public class net.bytebuddy.renamed.java.lang.Object$ByteBuddy$i8uhbcjD
minor version: 0
major version: 69
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #2 // net/bytebuddy/renamed/java/lang/Object$ByteBuddy$i8uhbcjD
super_class: #4 // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 0
Constant pool:
#1 = Utf8 net/bytebuddy/renamed/java/lang/Object$ByteBuddy$i8uhbcjD
#2 = Class #1 // net/bytebuddy/renamed/java/lang/Object$ByteBuddy$i8uhbcjD
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = NameAndType #5:#6 // "<init>":()V
#8 = Methodref #4.#7 // java/lang/Object."<init>":()V
#9 = Utf8 Code
{
public net.bytebuddy.renamed.java.lang.Object$ByteBuddy$i8uhbcjD();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
}
反编译的结果
由于这个 class 文件的内容并不复杂,我们可以尝试手动反编译。反编译的结果如下
java
// 以下内容是我反编译的结果,不保证绝对准确,仅供参考
package net.bytebuddy.renamed.java.lang;
public class Object$ByteBuddy$i8uhbcjD {
public Object$ByteBuddy$i8uhbcjD() {
super();
}
}
类图
类图如下 ⬇️ 
第二个例子: 在第一个例子的基础上指定包名和类名
我们在 类创建 里继续看第二个例子

文章中提供的代码如下 ⬇️
java
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
.subclass(Object.class)
.name("example.Type")
.make();
我们稍作调整,可以写出如下的 Main2.java
java
import net.bytebuddy.ByteBuddy;
import java.io.File;
import java.io.IOException;
public class Main2 {
public static void main(String[] args) throws IOException {
new ByteBuddy()
.subclass(Object.class)
.name("example.Type")
.make()
.saveIn(new File("."));
}
}
编译与运行
执行如下命令就可以编译 Main2.java 以及运行其中的 main 方法 ⬇️
bash
javac -cp byte-buddy-1.18.7.jar Main2.java
java -cp byte-buddy-1.18.7.jar:. Main2
此时再执行 tree . 命令,就会看到有新的目录/文件生成 ⬇️(我已经把第一个例子生成的目录/文件删掉了)
text
.
├── byte-buddy-1.18.7.jar
├── example
│ └── Type.class
├── Main.java
├── Main2.class
└── Main2.java
2 directories, 5 files
可见,Byte Buddy 生成了 example 目录,并在其中生成了 Type.class 文件。我们用如下的命令可以查看 Type.class 文件的内容
bash
javap -v -p example.Type
其输出如下 (我删掉了前三行)
text
public class example.Type
minor version: 0
major version: 69
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #2 // example/Type
super_class: #4 // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 0
Constant pool:
#1 = Utf8 example/Type
#2 = Class #1 // example/Type
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = NameAndType #5:#6 // "<init>":()V
#8 = Methodref #4.#7 // java/lang/Object."<init>":()V
#9 = Utf8 Code
{
public example.Type();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
}
反编译的结果
由于这个 class 文件的内容并不复杂,我们可以尝试手动反编译。反编译的结果如下
java
// 以下内容是我反编译的结果,不保证绝对准确,仅供参考
package example;
public class Type {
public Type() {
super();
}
}
类图
类图如下 ⬇️

第三个例子: 执行命名策略
我们在 类创建 里继续看第三个例子

文章中提供的代码如下 ⬇️
java
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
.with(new NamingStrategy.AbstractBase() {
@Override
protected String name(TypeDescription superClass) {
return "i.love.ByteBuddy." + superClass.getSimpleName();
}
})
.subclass(Object.class)
.make();
我们稍作调整,可以写出如下的 Main3.java
java
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.NamingStrategy;
import net.bytebuddy.description.type.TypeDescription;
import java.io.File;
import java.io.IOException;
public class Main3 {
public static void main(String[] args) throws IOException {
new ByteBuddy()
.with(new NamingStrategy.AbstractBase() {
@Override
protected String name(TypeDescription superClass) {
return "i.love.ByteBuddy." + superClass.getSimpleName();
}
})
.subclass(Object.class)
.make()
.saveIn(new File("."));
}
}
编译与运行
执行如下命令就可以编译 Main3.java 以及运行其中的 main 方法 ⬇️
bash
javac -cp byte-buddy-1.18.7.jar Main3.java
java -cp byte-buddy-1.18.7.jar:. Main3
此时再执行 tree . 命令,就会看到有新的目录/文件生成 ⬇️(我已经把前两个例子生成的目录/文件删掉了)
text
.
├── byte-buddy-1.18.7.jar
├── i
│ └── love
│ └── ByteBuddy
│ └── Object.class
├── Main.java
├── Main2.java
├── Main3.class
├── Main3.java
└── Main3$1.class
4 directories, 7 files
因为我们的代码里用到了匿名内部类,所以会生成 Main3$1.class 文件。而 i/love/ByteByddy/Object.class 看起来是 Byte Buddy 生成的。我们用如下的命令可以查看 Type.class 文件的内容
bash
javap -v -p i/love/ByteBuddy/Object.class
其输出如下 (我删掉了前三行)
text
public class i.love.ByteBuddy.Object
minor version: 0
major version: 69
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #2 // i/love/ByteBuddy/Object
super_class: #4 // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 0
Constant pool:
#1 = Utf8 i/love/ByteBuddy/Object
#2 = Class #1 // i/love/ByteBuddy/Object
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = NameAndType #5:#6 // "<init>":()V
#8 = Methodref #4.#7 // java/lang/Object."<init>":()V
#9 = Utf8 Code
{
public i.love.ByteBuddy.Object();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
}
反编译的结果
由于这个 class 文件的内容并不复杂,我们可以尝试手动反编译。反编译的结果如下
java
// 以下内容是我反编译的结果,不保证绝对准确,仅供参考
package i.love.ByteBuddy;
public class Object {
public Object() {
super();
}
}
类图
类图如下 ⬇️

其他
用 PlantUML 画图,所用到的代码列举如下
画 "第一个例子的类图" 所用到的代码
puml
@startuml
title 第一个例子的类图
class net.bytebuddy.renamed.java.lang.Object$ByteBuddy$i8uhbcjD {
+ Object$ByteBuddy$i8uhbcjD()
}
note right of net.bytebuddy.renamed.java.lang.Object$ByteBuddy$i8uhbcjD::Object$ByteBuddy$i8uhbcjD
这是构造函数
end note
@enduml
画 "第二个例子的类图" 所用到的代码
puml
@startuml
title 第二个例子的类图
class example.Type {
+ Type()
}
note right of example.Type::Type
这是构造函数
end note
@enduml
画 "第三个例子的类图" 所用到的代码
puml
@startuml
title 第三个例子的类图
class i.love.ByteBuddy.Object {
+ Object()
}
note right of i.love.ByteBuddy.Object::Object
这是构造函数
end note
@enduml
参考资料
- Creating a class (这篇是英文)
- 类创建 (这篇是中文)