背景
在 Byte Buddy 网站 提供的 tutorial 里,可以找到 Fields and methods 这一小节
以及对应的中文版 ⬇️ 字段和方法
我想看看 Byte Buddy 在这一小节的各个示例代码里所生成的类的结构是怎样的,所以写了本文来进行记录。为了使行文简洁,我把 javap 命令对 class 文件的解析结果统一保存在 这篇笔记 里了。让我们开始探索吧。
正文
如何下载 Byte Buddy 的 jar 包可以参考 Byte Buddy 生成的类的结构如何?(第一篇) 一文。
第一个例子:生成 Object 的子类
第一个例子的位置如下图所示 ⬇️
代码如下
String toString = new ByteBuddy()
.subclass(Object.class)
.name("example.Type")
.make()
.load(getClass().getClassLoader())
.getLoaded()
.newInstance() // Java reflection API
.toString();
出于以下考虑,我在上述代码的基础上写了 Example1.java
- 让代码可以直接编译和运行
- 让
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();
}
}
类图
类图如下 ⬇️
第二个例子:让 toString() 方法返回固定值
第二个例子的位置如下图所示 ⬇️
代码如下
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
- 让代码可以直接编译和运行
- 让
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();
}
}
类图
类图如下 ⬇️
第三个例子:用更多的约束条件来指定方法
第三个例子的位置如下图所示 ⬇️
代码如下
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();
从文中的解释来看,这是使用了更多的约束条件来指定方法。文中提到了,这些规则生效的顺序问题 ⬇️
出于以下考虑,我在上述代码的基础上写了 Example3.java
- 让代码可以直接编译和运行
- 让
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();
}
}
类图
类图如下 ⬇️
第四个例子:尝试使用不兼容的类型
第四个例子的位置如下图所示 ⬇️
代码如下 ⬇️
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)
第五个例子:委托方法调用
第五个例子和委托方法调用有关,它的位置如下图所示 ⬇️
代码如下 ⬇️
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
- 让代码可以直接编译和运行
- 让
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();
}
}
类图
类图如下 ⬇️
其他
用 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