Byte Buddy 生成的类的结构如何?(第一篇)

背景

Byte Buddy 的 网站 上可以找到以下文章

在文章中,我们可以看到很多具体的例子。其中一个例子如下

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

参考资料

Byte Buddy 的 网站 上的

相关推荐
sxhcwgcy2 小时前
Spring Cloud GateWay搭建
java
umeelove352 小时前
Spring框架
java·后端·spring
轩情吖2 小时前
MySQL之表的约束
android·数据库·c++·后端·mysql·开发·约束
xjt_09012 小时前
用 LiteLLM 打通 Codex CLI 与 Claude Code(有key即可实现编程自由)
后端·python·flask
Tzarevich2 小时前
Agent记忆模块:让大模型“记住”你,还能省Token!
后端·langchain·agent
baizhigangqw2 小时前
SpringBoot中整合ONLYOFFICE在线编辑
java·spring boot·后端
pangares2 小时前
Spring Boot文件上传
java·spring boot·后端
zhglhy2 小时前
Java分布式链路技术
java·分布式·分布式链路
大黄说说2 小时前
RESTful API vs GraphQL:设计哲学、性能博弈与选型指南
后端·restful·graphql