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

5 阅读5分钟

背景

Byte Buddy 网站 提供的 tutorial 里,可以找到 Fields and methods 这一小节

image.png

以及对应的中文版 ⬇️ 字段和方法

image.png

我想看看 Byte Buddy 在这一小节的各个示例代码里所生成的类的结构是怎样的,所以写了本文来进行记录。为了使行文简洁,我把 javap 命令对 class 文件的解析结果统一保存在 这篇笔记 里了。让我们开始探索吧。

正文

如何下载 Byte Buddyjar 包可以参考 Byte Buddy 生成的类的结构如何?(第一篇) 一文。

第一个例子:生成 Object 的子类

第一个例子的位置如下图所示 ⬇️

image.png

代码如下

String toString = new ByteBuddy()
  .subclass(Object.class)
  .name("example.Type")
  .make()
  .load(getClass().getClassLoader())
  .getLoaded()
  .newInstance() // Java reflection API
  .toString();

出于以下考虑,我在上述代码的基础上写了 Example1.java

  1. 让代码可以直接编译和运行
  2. Byte Buddy 所生成的类以 class 文件的形式保存在本地文件中

Example1.java 的内容如下 ⬇️

import net.bytebuddy.ByteBuddy;

import java.io.File;
import java.io.IOException;

public class Example1 {
    public static void main(String[] args) throws IOException {
        new ByteBuddy()
                .subclass(Object.class)
                .name("example.Type")
                .make()
                .saveIn(new File("."));
    }
}

编译与运行

执行如下命令就可以编译 Example1.java 以及运行其中的 main 方法 ⬇️

javac -cp byte-buddy-1.18.7.jar Example1.java
java -cp byte-buddy-1.18.7.jar:. Example1

此时再执行 tree . 命令,就会看到有新的目录/文件生成 ⬇️

.
├── byte-buddy-1.18.7.jar
├── example
│   └── Type.class
├── Example1.class
└── Example1.java

2 directories, 4 files

我们用如下的命令可以查看这个 Type.class 文件的内容

javap -v -p example/Type.class

命令的执行结果可以参考 这篇笔记

反编译的结果

由于这个 class 文件的内容并不复杂,我们可以尝试手动反编译。反编译的结果如下

// 以下内容是我反编译的结果,不保证绝对准确,仅供参考
package example;

public class Type {
    public Type {
        super();
    }
}

类图

类图如下 ⬇️

image.png

第二个例子:让 toString() 方法返回固定值

第二个例子的位置如下图所示 ⬇️
image.png

代码如下

String toString = new ByteBuddy()
  .subclass(Object.class)
  .name("example.Type")
  .method(named("toString")).intercept(FixedValue.value("Hello World!"))
  .make()
  .load(getClass().getClassLoader())
  .getLoaded()
  .newInstance()
  .toString();

出于以下考虑,我在上述代码的基础上写了 Example2.java

  1. 让代码可以直接编译和运行
  2. Byte Buddy 所生成的类以 class 文件的形式保存在本地文件中

Example2.java 的内容如下 ⬇️

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.FixedValue;

import java.io.File;
import java.io.IOException;

import static net.bytebuddy.matcher.ElementMatchers.named;

public class Example2 {
    public static void main(String[] args) throws IOException {
        new ByteBuddy()
                .subclass(Object.class)
                .name("example.Type")
                .method(named("toString")).intercept(FixedValue.value("Hello World!"))
                .make()
                .saveIn(new File("."));
    }
}

编译与运行

执行如下命令就可以编译 Example2.java 以及运行其中的 main 方法 ⬇️

javac -cp byte-buddy-1.18.7.jar Example2.java
java -cp byte-buddy-1.18.7.jar:. Example2

此时再执行 tree . 命令,就会看到有新的目录/文件生成 ⬇️

.
├── byte-buddy-1.18.7.jar
├── example
│   └── Type.class
├── Example2.class
└── Example2.java

2 directories, 4 files

我们用如下的命令可以查看这个 Type.class 文件的内容

javap -v -p example/Type.class

命令的执行结果可以参考 这篇笔记

反编译的结果

由于这个 class 文件的内容并不复杂,我们可以尝试手动反编译。反编译的结果如下

// 以下内容是我反编译的结果,不保证绝对准确,仅供参考
package example;

public class Type {
    public String toString() {
        return "Hello World!";
    }
    
    public Type() {
        super();
    }
}

类图

类图如下 ⬇️

image.png

第三个例子:用更多的约束条件来指定方法

第三个例子的位置如下图所示 ⬇️

image.png

代码如下

named("toString").and(returns(String.class)).and(takesArguments(0))
class Foo {
  public String bar() { return null; }
  public String foo() { return null; }
  public String foo(Object o) { return null; }
}
 
Foo dynamicFoo = new ByteBuddy()
  .subclass(Foo.class)
  .method(isDeclaredBy(Foo.class)).intercept(FixedValue.value("One!"))
  .method(named("foo")).intercept(FixedValue.value("Two!"))
  .method(named("foo").and(takesArguments(1))).intercept(FixedValue.value("Three!"))
  .make()
  .load(getClass().getClassLoader())
  .getLoaded()
  .newInstance();

从文中的解释来看,这是使用了更多的约束条件来指定方法。文中提到了,这些规则生效的顺序问题 ⬇️

image.png

出于以下考虑,我在上述代码的基础上写了 Example3.java

  1. 让代码可以直接编译和运行
  2. Byte Buddy 所生成的类以 class 文件的形式保存在本地文件中

Example3.java 的内容如下 ⬇️

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.FixedValue;

import java.io.File;
import java.io.IOException;

import static net.bytebuddy.matcher.ElementMatchers.*;

public class Example3 {
    public static class Foo {
        public String bar() {
            return null;
        }

        public String foo() {
            return null;
        }

        public String foo(Object o) {
            return null;
        }
    }

    public static void main(String[] args) throws IOException {
        new ByteBuddy()
                .subclass(Foo.class)
                .name("example.Type") // 原本的例子中没有这一行
                .method(isDeclaredBy(Foo.class))
                .intercept(FixedValue.value("One!"))
                .method(named("foo"))
                .intercept(FixedValue.value("Two!"))
                .method(named("foo").and(takesArguments(1)))
                .intercept(FixedValue.value("Three!"))
                .make()
                .saveIn(new File("."));
    }
}

编译与运行

执行如下命令就可以编译 Example3.java 以及运行其中的 main 方法 ⬇️

javac -cp byte-buddy-1.18.7.jar Example3.java
java -cp byte-buddy-1.18.7.jar:. Example3

此时再执行 tree . 命令,就会看到有新的目录/文件生成 ⬇️

.
├── byte-buddy-1.18.7.jar
├── example
│   └── Type.class
├── Example3.class
├── Example3.java
└── Example3$Foo.class

2 directories, 5 files

我们用如下的命令可以查看这个 Type.class 文件的内容

javap -v -p example.Type

命令的执行结果可以参考 这篇笔记

反编译的结果

由于这个 class 文件的内容并不复杂,我们可以尝试手动反编译。反编译的结果如下

// 以下内容是我反编译的结果,不保证绝对准确,仅供参考
package example;

public class Type extends Example3$Foo {
    public String bar() {
        return "One!";
    }
    
    public String foo(Object arg0) {
        return "Three!";
    }

    public String foo() {
        return "Two!";
    }
    
    public Type() {
        super();
    }
}

类图

类图如下 ⬇️

image.png

第四个例子:尝试使用不兼容的类型

第四个例子的位置如下图所示 ⬇️ image.png

代码如下 ⬇️

new ByteBuddy()
  .subclass(Foo.class)
  .method(isDeclaredBy(Foo.class)).intercept(FixedValue.value(0))
  .make();

看文中的描述,这个例子应该会抛出异常。为了让它能运行,我们把程序补充完整 ⬇️

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.FixedValue;

import static net.bytebuddy.matcher.ElementMatchers.*;

class Foo {
    public String bar() {
        return null;
    }

    public String foo() {
        return null;
    }

    public String foo(Object o) {
        return null;
    }
}

public class Example4 {
    public static void main(String[] args) {
        new ByteBuddy()
                .subclass(Foo.class)
                .method(isDeclaredBy(Foo.class)).intercept(FixedValue.value(0))
                .make();
    }
}

请将以上代码保存为 Example4.java

编译与运行

执行如下命令就可以编译 Example4.java 以及运行其中的 main 方法 ⬇️

javac -cp byte-buddy-1.18.7.jar Example4.java
java -cp byte-buddy-1.18.7.jar:. Example4

我在运行 main 方法时,看到的报错信息如下

Exception in thread "main" java.lang.IllegalArgumentException: Cannot return value of type int for public java.lang.String Foo.bar()
	at net.bytebuddy.implementation.FixedValue.apply(FixedValue.java:217)
	at net.bytebuddy.implementation.FixedValue$ForConstantValue.apply(FixedValue.java:589)
	at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyCode(TypeWriter.java:766)
	at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyBody(TypeWriter.java:751)
	at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod.apply(TypeWriter.java:658)
	at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForCreation.create(TypeWriter.java:6318)
	at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:2280)
	at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$UsingTypeWriter.make(DynamicType.java:4882)
	at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:4545)
	at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Delegator.make(DynamicType.java:4818)
	at Example4.main(Example4.java:25)

第五个例子:委托方法调用

第五个例子和委托方法调用有关,它的位置如下图所示 ⬇️

image.png

代码如下 ⬇️

class Source {
  public String hello(String name) { return null; }
}
 
class Target {
  public static String hello(String name) {
    return "Hello " + name + "!";
  }
}
 
String helloWorld = new ByteBuddy()
  .subclass(Source.class)
  .method(named("hello")).intercept(MethodDelegation.to(Target.class))
  .make()
  .load(getClass().getClassLoader())
  .getLoaded()
  .newInstance()
  .hello("World");

出于以下考虑,我在上述代码的基础上写了 Example5.java

  1. 让代码可以直接编译和运行
  2. Byte Buddy 所生成的类以 class 文件的形式保存在本地文件中

Example5.java 的内容如下 ⬇️

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.MethodDelegation;

import java.io.File;
import java.io.IOException;

import static net.bytebuddy.matcher.ElementMatchers.*;

class Source {
    public String hello(String name) {
        return null;
    }
}

class Target {
    public static String hello(String name) {
        return "Hello " + name + "!";
    }
}

public class Example5 {

    public static void main(String[] args) throws IOException {
        new ByteBuddy()
                .subclass(Source.class)
                .method(named("hello")).intercept(MethodDelegation.to(Target.class))
                .make()
                .saveIn(new File("."));
    }
}

编译与运行

执行如下命令就可以编译 Example5.java 以及运行其中的 main 方法 ⬇️

javac -cp byte-buddy-1.18.7.jar Example5.java
java -cp byte-buddy-1.18.7.jar:. Example5

此时再执行 tree . 命令,就会看到有新的文件生成 ⬇️

.
├── byte-buddy-1.18.7.jar
├── Example5.class
├── Example5.java
├── Source.class
├── Source$ByteBuddy$D8Rh7iI7.class
└── Target.class

1 directory, 6 files

我们用如下的命令可以查看这个 Source$ByteBuddy$D8Rh7iI7.class 文件的内容 (在您的电脑上,Byte Buddy 所生成的 class 文件可能是别的名称)

javap -v -p Source\$ByteBuddy\$D8Rh7iI7.class

命令的执行结果可以参考 这篇笔记

反编译的结果

由于这个 class 文件的内容并不复杂,我们可以尝试手动反编译。反编译的结果如下

// 以下内容是我反编译的结果,不保证绝对准确,仅供参考
public class Source$ByteBuddy$D8Rh7iI7 extends Source {
    public String hello(String arg0) {
        return Target.hell();
    }

    public Source$ByteBuddy$D8Rh7iI7() {
        super();
    }
}

类图

类图如下 ⬇️

image.png

其他

用 PlantUML 画图,所用到的代码列举如下

画 "第一个例子的类图" 所用到的代码

@startuml

title 第一个例子的类图

class example.Type {
    + Type()
}

note right of example.Type::Type
这是构造函数
end note

@enduml

画 "第二个例子的类图" 所用到的代码

@startuml

title 第二个例子的类图

class example.Type {
    + String toString()
    + Type()
}

note left of example.Type::toString()
<code>
public String toString() {
    return "Hello World!";
}
</code>
end note

@enduml

画 "第三个例子的类图" 所用到的代码

@startuml

title 第三个例子的类图

class Example3$Foo {
    + Example3$Foo()
    + String bar()
    + String foo()
    + String foo(Object)
}

class example.Type {
    + String bar()
    + String foo(Object)
    + String foo()
    + Type()
}

Example3$Foo <|-- example.Type

note left of example.Type::bar
以下代码是我反编译的结果, 内容仅供参考
<code>
public String bar() {
    return "One!";
}
</code>
end note

note left of example.Type::"foo(Object)"
以下代码是我反编译的结果, 内容仅供参考
<code>
public String foo(Object arg0) {
    return "Three!";
}
</code>
end note

note left of example.Type::"foo()"
以下代码是我反编译的结果, 内容仅供参考
<code>
public String foo() {
    return "Two!";
}
</code>
end note

@enduml

画 "第五个例子的类图" 所用到的代码

@startuml

title 第五个例子的类图

class Source
class Target
class Source$ByteBuddy$D8Rh7iI7

Source <|-- Source$ByteBuddy$D8Rh7iI7

class Source {
    + String hello(String)
}

class Target {
    + {static} String hello(String)
}

class Source$ByteBuddy$D8Rh7iI7 {
    + String hello(String)
    + Source$ByteBuddy$D8Rh7iI7()
}

note left of Source::hello
<code>
public String hello(String name) {
    return null;
}
</code>
end note

note right of Target::hello
<code>
public static String hello(String name) {
    return "Hello " + name + "!";
}
</code>
end note

note left of Source$ByteBuddy$D8Rh7iI7::hello
以下内容是我反编译的结果,不保证绝对准确,仅供参考
<code>
public String hello(String arg0) {
    return Target.hell();
}
</code>
end note

@enduml

参考资料