背景
在 [Java] 从 class 文件看动态代理 一文中,我们分析了 JDK 所生成的代理类的结构。那么在使用 Byte Buddy 时,是否也可以使用 InvocationHandler 呢?如果可以的话,所生成的代理类又会是什么样子呢?让我们一起来探索吧。
要点
通过观察代理类的 class 文件,会发现
- 代理类中有一个 类型的静态字段(我们简称为 吧)
- 这个静态字段 和我们指定的 实例(我们把后者简称为 吧)是同一个对象
- 以 中的
run()方法为例,如果我们执行代理类中的run()方法,那么它会调用 的invoke(Object, Method, Object[])方法,即 的invoke(Object, Method, Object[])方法
项目结构
我们用一个小项目来进行探索。在这个项目顶层执行 tree . 命令,可以看到如下结果 ⬇️
.
├── pom.xml
└── src
└── main
└── java
└── org
└── example
└── MyProxy.java
6 directories, 2 files
MyProxy.java
其中 MyProxy.java 的内容如下 ⬇️
package org.example;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.reflect.InvocationHandler;
public class MyProxy {
public static void main(String[] args) throws ReflectiveOperationException {
// Create a naive InvocationHandler
InvocationHandler handler = (proxy, method, params) -> {
System.out.println("Hello world");
return null;
};
// Generate the proxy class
Class<? extends Runnable> proxyClass = new ByteBuddy()
.subclass(Runnable.class)
.method(ElementMatchers.any())
.intercept(InvocationHandlerAdapter.of(handler))
.make()
.load(MyProxy.class.getClassLoader())
.getLoaded();
// Create a new instance of the proxy
Runnable runnable = proxyClass.getDeclaredConstructor().newInstance();
runnable.run();
}
}
说明:MyProxy.java 中的代码并不是我原创的。我在 Google 搜了 byte buddy invocationhandler example 就能看到比较有用的代码示例 ⬇️,我是在这些代码的基础上写的 MyProxy.java
pom.xml
pom.xml 的内容如下 ⬇️
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>byte-byddy-study</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.18.7</version>
</dependency>
</dependencies>
</project>
运行结果
运行 MyProxy 中的 main 方法,可以看到如下的结果 ⬇️
Hello world
分析
MyProxy 类的 main 方法
运行 main 方法时,发生了什么呢?一共发生了三件事 ⬇️
MyProxyV2: 将代理类保存至 class 文件中
Byte Buddy 生成了代理类,但是我们暂时看不到这个代理类长什么样子。我们在 MyProxy.java 的基础上可以写出如下的代码 ⬇️ (请将如下代码保存为 MyProxyV2.java,并将其保存到何 MyProxy.java 平级的位置)
package org.example;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.bytebuddy.matcher.ElementMatchers;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
public class MyProxyV2 {
public static void main(String[] args) throws IOException {
// Create a naive InvocationHandler
InvocationHandler handler = (proxy, method, params) -> {
System.out.println("Hello world");
return null;
};
// Generate and save the proxy class
new ByteBuddy()
.subclass(Runnable.class)
.name("org.example.SimpleProxy")
.method(ElementMatchers.any())
.intercept(InvocationHandlerAdapter.of(handler))
.make()
.saveIn(new File("."));
}
}
现在执行 tree . 命令,会看到如下的结果
.
├── pom.xml
└── src
└── main
└── java
└── org
└── example
├── MyProxy.java
└── MyProxyV2.java
6 directories, 3 files
执行 MyProxyV2 中的 main 方法,它不会在控制台输出文字,但是我们会看到当前目录下多了一个 org 目录。我执行 tree org 命令后,看到的结果如下
org
└── example
└── SimpleProxy.class
2 directories, 1 file
我们可以用 javap 命令自行分析 class 文件的内容 ⬇️
javap -v -p org.example.SimpleProxy
但是这样的工作太枯燥了,而且手动反编译很容易出错,所以我们还是直接借助 Intellij IDEA (Community Edition) 来查看这个 class 文件反编译的结果吧 ⬇️
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.example;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class SimpleProxy implements Runnable {
// $FF: synthetic field
public static volatile InvocationHandler invocationHandler$26p2530;
// $FF: synthetic field
private static final Method cachedValue$qnsSwswr$4cscpe1 = Object.class.getMethod("toString");
// $FF: synthetic field
private static final Method cachedValue$qnsSwswr$m7m8693 = Runnable.class.getMethod("run");
// $FF: synthetic field
private static final Method cachedValue$qnsSwswr$5j4bem0 = Object.class.getMethod("equals", Object.class);
// $FF: synthetic field
private static final Method cachedValue$qnsSwswr$7m9oaq0 = Object.class.getDeclaredMethod("clone");
// $FF: synthetic field
private static final Method cachedValue$qnsSwswr$9pqdof1 = Object.class.getMethod("hashCode");
public boolean equals(Object var1) {
return (Boolean)invocationHandler$26p2530.invoke(this, cachedValue$qnsSwswr$5j4bem0, new Object[]{var1});
}
public String toString() {
return (String)invocationHandler$26p2530.invoke(this, cachedValue$qnsSwswr$4cscpe1, (Object[])null);
}
public int hashCode() {
return (Integer)invocationHandler$26p2530.invoke(this, cachedValue$qnsSwswr$9pqdof1, (Object[])null);
}
protected Object clone() throws CloneNotSupportedException {
return invocationHandler$26p2530.invoke(this, cachedValue$qnsSwswr$7m9oaq0, (Object[])null);
}
public void run() {
invocationHandler$26p2530.invoke(this, cachedValue$qnsSwswr$m7m8693, (Object[])null);
}
public SimpleProxy() {
super();
}
}
为了方便理解,我画了对应的类图 ⬇️
中定义了 6 个字段和 5 个方法(如果不考虑构造函数的话)
大致浏览一下反编译的结果,会发现这 5 个方法的逻辑是类似的,所以我们仔细看其中一个就够了。我们以 run() 方法为例,细看一下它背后发生了什么。
run() 方法的内容只有一点点 ⬇️
public void run() {
invocationHandler$26p2530.invoke(this, cachedValue$qnsSwswr$m7m8693, (Object[])null);
}
其中 invocationHandler$26p2530 和 cachedValue$qnsSwswr$m7m8693 具体是什么,我在下图中用红色框标记出来了 ⬇️
由此可见
invocationHandler$26p2530是 的一个实例cachedValue$qnsSwswr$m7m8693是 的一个实例- 它代表了 中的
run()方法
- 它代表了 中的
那么 invocationHandler$26p2530 具体是什么呢?既然它是 的实例,那么它会不会就是 MyProxyV2 类 main 方法里的
handler 呢?我们写点代码验证一下 ⬇️ (请将如下代码保存为 MyProxyV3.java,并将其保存到何 MyProxy.java 平级的位置)
MyProxyV3: 验证 InvocationHandler 来自哪里
package org.example;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.bytebuddy.matcher.ElementMatchers;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
public class MyProxyV3 {
public static void main(String[] args) throws IOException, IllegalAccessException {
// Create a naive InvocationHandler
InvocationHandler handler = (proxy, method, params) -> {
System.out.println("Hello world");
return null;
};
final String proxyClassName = "org.example.SimpleProxy";
// Generate and the proxy class
Class<? extends Runnable> proxyClass = new ByteBuddy()
.subclass(Runnable.class)
.name(proxyClassName)
.method(ElementMatchers.any())
.intercept(InvocationHandlerAdapter.of(handler))
.make()
.load(MyProxy.class.getClassLoader())
.getLoaded();
Field[] fields = proxyClass.getFields();
for (Field field : fields) {
if (field.getType() == InvocationHandler.class) {
String message =
String.format(
"%s 里的 InvocationHandler 类型的字段和当前 main 方法里的 handler 是同一个对象吗? ⬇️",
proxyClassName
);
System.out.println(message);
System.out.println(field.get(null) == handler);
}
}
}
}
现在执行 tree src 命令,会看到如下的结果
src
└── main
└── java
└── org
└── example
├── MyProxy.java
├── MyProxyV2.java
└── MyProxyV3.java
5 directories, 3 files
运行 MyProxyV3 中的 main 方法,会看到如下的结果 ⬇️
org.example.SimpleProxy 里的 InvocationHandler 类型的字段和当前 main 方法里的 handler 是同一个对象吗? ⬇️
true
由此可见,我们的猜测是正确的,也就是说,下面两个对象引用的是同一个东西
- 里的 类型的字段
MyProxyV3类main方法里的handler
此时再看看 MyProxyV2 ⬇️ 就能 main 方法里的 handler 是如何起作用了
其他
画 "MyProxy 的 main 方法做了什么?" 这张图所用到的代码
我借助了 PlantUML 的插件来画那张图,用到的代码如下 ⬇️
@startwbs
'https://plantuml.com/wbs-diagram
title <i>MyProxy</i> 的 <i>main</i> 方法做了什么?
* <i>MyProxy</i> 的 <i>main</i> 方法做了什么?
** 1. 创建 <i>InvocationHandler</i> 的实例: <i>handler</i>
** 2. 借助 <i>Byte Buddy</i> 生成 <i>Runnable</i> 的一个动态代理类
*** 一些准备工作 (本文不关心其中的细节)
*** 将这个动态代理类的 <i>Class</i> 对象保存在 <b><i>proxyClass</i></b> 里
**:3. 借助 <b><i>proxyClass</i></b> 生成代理类的一个实例 <i>runnable</i>
调用 <i>runnable</i> 的 <i>run()</i> 方法;
@endwbs
画 "org.example.SimpleProxy 的类图" 所用到的代码
我借助了 PlantUML 的插件来画那张图,用到的代码如下 ⬇️
@startuml
'https://plantuml.com/class-diagram
title <i>org.example.SimpleProxy</i> 的类图
interface java.lang.Runnable
class org.example.SimpleProxy
java.lang.Runnable <|.. org.example.SimpleProxy
class org.example.SimpleProxy {
+ {static} volatile InvocationHandler invocationHandler$26p2530
- {static} final Method cachedValue$qnsSwswr$4cscpe1
- {static} final Method cachedValue$qnsSwswr$m7m8693
- {static} final Method cachedValue$qnsSwswr$5j4bem0
- {static} final Method cachedValue$qnsSwswr$7m9oaq0
- {static} final Method cachedValue$qnsSwswr$9pqdof1
+ boolean equals(Object var1)
+ String toString()
+ int hashCode()
# Object clone() throws CloneNotSupportedException
+ void run()
+ SimpleProxy()
}
@enduml