背景
在 Byte Buddy 网站 提供的 tutorial 里,可以找到 Fields and methods 这一小节
以及对应的中文版 ⬇️ 字段和方法
我想看看 Byte Buddy 在这一小节的各个示例代码里所生成的类的结构是怎样的,所以写了本文来进行记录。为了使行文简洁,我把 javap 命令对 class 文件的解析结果统一保存在 这篇笔记 里了。让我们开始探索吧。
本文的七个例子
- 第一个例子:生成
Object的子类 - 第二个例子:让
toString()方法返回固定值 - 第三个例子:用更多的约束条件来指定方法
- 第四个例子:尝试使用不兼容的类型
- 第五个例子:委托方法调用
- 第六个例子:委托方法调用(从多个候选者中选择某一个)
- 第七个例子:使用
@Argument注解
出于以下考虑,我会在 tutorial 所提供代码的基础上,写出调整后的 java 代码
- 让代码可以直接编译和运行
- 让
Byte Buddy所生成的类以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 ⬇️
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 ⬇️
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 ⬇️
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 ⬇️
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.hello(arg0);
}
public Source$ByteBuddy$D8Rh7iI7() {
super();
}
}
类图
类图如下 ⬇️
第六个例子:委托方法调用(从多个候选者中选择某一个)
第六个例子仍旧和委托方法调用有关,它的位置如下图所示 ⬇️
代码如下 👇
class Target {
public static String intercept(String name) { return "Hello " + name + "!"; }
public static String intercept(int i) { return Integer.toString(i); }
public static String intercept(Object o) { return o.toString(); }
}
结合第五个例子中的 Example5.java,我们可以写出 Example6.java 👇
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.MethodDelegation;
import java.io.File;
import java.io.IOException;
import static net.bytebuddy.matcher.ElementMatchers.named;
class Source {
public String hello(String name) {
return null;
}
}
class Target {
public static String intercept(String name) {
return "Hello " + name + "!";
}
public static String intercept(int i) {
return Integer.toString(i);
}
public static String intercept(Object o) {
return o.toString();
}
}
public class Example6 {
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("."));
}
}
编译与运行
执行如下命令就可以编译 Example6.java 以及运行其中的 main 方法 ⬇️
javac -cp byte-buddy-1.18.7.jar Example6.java
java -cp byte-buddy-1.18.7.jar:. Example6
此时再执行 tree . 命令,就会看到有新的文件生成 ⬇️
.
├── byte-buddy-1.18.7.jar
├── Example6.class
├── Example6.java
├── Source.class
├── Source$ByteBuddy$bvY4Mgvy.class
└── Target.class
1 directory, 6 files
我们用如下的命令可以查看 Source$ByteBuddy$bvY4Mgvy.class 文件的内容 (在您的电脑上,Byte Buddy 所生成的 class 文件可能是别的名称)
javap -v -p Source\$ByteBuddy\$bvY4Mgvy.class
命令的执行结果可以参考 这篇笔记
反编译的结果
由于这个 class 文件的内容并不复杂,我们可以尝试手动反编译。反编译的结果如下
// 以下内容是我反编译的结果,仅供参考
public class Source$ByteBuddy$bvY4Mgvy extends Source {
public String hello(String arg0) {
return Target.intercept(arg0);
}
public Source$ByteBuddy$bvY4Mgvy() {
super();
}
}
类图
类图如下 ⬇️
第七个例子:使用 @Argument 注解
第七个例子和使用 @Argument 注解有关,它的位置如下图所示 ⬇️
文中提供的代码只有以下两行
void foo(Object o1, Object o2)
void foo(@Argument(0) Object o1, @Argument(1) Object o2)
如果想运行它,需要自己补充一些代码,我把它改造成了这个样子 ⬇️
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.Argument;
import java.io.File;
import java.io.IOException;
import static net.bytebuddy.matcher.ElementMatchers.*;
class Source {
public String hello(String s, int a) {
return null;
}
}
class Target {
public static String foo(@Argument(1) String s, @Argument(0) int a) {
return "Hello world from foo(String, int)";
}
public static String foo(@Argument(1) int a, @Argument(0) String s) {
return "Hello world from foo(int, String)";
}
}
public class Example7 {
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("."));
}
}
编译与运行
执行如下命令就可以编译 Example7.java 以及运行其中的 main 方法 ⬇️
javac -cp byte-buddy-1.18.7.jar -parameters Example7.java
java -cp byte-buddy-1.18.7.jar:. Example7
此时再执行 tree . 命令,就会看到有新的文件生成 ⬇️
.
├── byte-buddy-1.18.7.jar
├── Example7.class
├── Example7.java
├── Source.class
├── Source$ByteBuddy$QoRXerIc.class
└── Target.class
1 directory, 6 files
我们用如下的命令可以查看 Source$ByteBuddy$QoRXerIc.class 文件的内容 (在您的电脑上,Byte Buddy 所生成的 class 文件可能是别的名称)
javap -v -p Source\$ByteBuddy\$QoRXerIc.class
命令的执行结果可以参考 这篇笔记
反编译的结果
由于这个 class 文件的内容并不复杂,我们可以尝试手动反编译。反编译的结果如下
// 以下内容是我反编译的结果,仅供参考
public class Source$ByteBuddy$QoRXerIc extends Source {
public String hello(String s, int a) {
return Target.foo(a, s);
}
public Source$ByteBuddy$RvyMqCBO() {
super();
}
}
从反编译的结果可以看出,Source$ByteBuddy$QoRXerIc 这个类的 hello(String, int) 方法里调用的是 Target 类中的 foo(int, String) 方法(而不是 foo(String, int) 方法,虽然乍看上去后者更匹配)。这是因为我们通过使用 @Argument 注解,指定了被拦截的方法(即 hello(String, int) 方法)中,参数的下标编号。
我在 @Argument 的 javadoc 里找到了如下描述,可供参考
类图
类图如下 ⬇️
其他
用 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.hello(arg0);
}
</code>
end note
@enduml
画 "第六个例子的类图" 所用到的代码
@startuml
title 第六个例子的类图
class Source
class Target
class Source$ByteBuddy$bvY4Mgvy
Source <|-- Source$ByteBuddy$bvY4Mgvy
class Source {
+ String hello(String)
}
class Target {
+ {static} String intercept(String)
+ {static} String intercept(int)
+ {static} String intercept(Object)
}
class Source$ByteBuddy$bvY4Mgvy {
+ String hello(String)
+ Source$ByteBuddy$bvY4Mgvy()
}
note left of Source$ByteBuddy$bvY4Mgvy::hello
以下内容是我反编译的结果,仅供参考
<code>
public String hello(String arg0) {
return Target.intercept(arg0);
}
</code>
end note
note left of Source$ByteBuddy$bvY4Mgvy::Source$ByteBuddy$bvY4Mgvy
这个是构造函数
end note
@enduml
画 "第七个例子的类图" 所用到的代码
@startuml
title 第七个例子的类图
class Source
class Target
class Source$ByteBuddy$QoRXerIc
Source <|-- Source$ByteBuddy$QoRXerIc
class Source {
+ String hello(String, int)
}
class Target {
+ {static} String foo(String, int)
+ {static} String foo(int, String)
}
class Source$ByteBuddy$QoRXerIc {
+ String hello(String, int)
+ Source$ByteBuddy$QoRXerIc()
}
note left of Source$ByteBuddy$QoRXerIc::hello
<b>以下内容是我手动反编译的结果,仅供参考</b>
<code>
public String hello(String s, int a) {
return Target.foo(a, s);
}
</code>
end note
note left of Source$ByteBuddy$QoRXerIc::Source$ByteBuddy$QoRXerIc
它是构造函数
end note
note left of Target::"foo(String, int)"
<code>
public static String foo(@Argument(1) String s, @Argument(0) int a) {
return "Hello world from foo(String, int)";
}
</code>
end note
note left of Target::"foo(int, String)"
<code>
public static String foo(@Argument(1) int a, @Argument(0) String s) {
return "Hello world from foo(int, String)";
}
</code>
end note
@enduml