背景
如 [Java] 从 class 文件看 cglib 对 InvocationHandler 的处理 一文所提到的, 使用 cglib 时,会用到 的子接口 ⬇️
它们的简要类图如下 ⬇️
本文的主角是 ,下图单独展示了 和 的关系 👇
要点
代码
铺垫
假设我们已经写好了一个简单的 Adder ⬇️
public class Adder {
public int add(int a, int b) {
return a + b;
}
}
现在希望在每次调用 add(int, int) 方法前,都把入参打印出来,那么可以通过动态代理来实现这一功能。
项目结构
我们在项目顶层执行 tree . 命令,会看到如下的结果 ⬇️
.
├── pom.xml
└── src
├── main
│ └── java
│ └── org
│ └── example
│ ├── Adder.java
│ └── AdderFactory.java
└── test
└── java
└── org
└── example
└── AdderFactoryTest.java
10 directories, 4 files
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>cglib-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>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.5</version> <!-- Use a recent version -->
<configuration>
<argLine>--add-opens=java.base/java.lang=ALL-UNNAMED</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>
Adder.java
package org.example;
public class Adder {
public int add(int a, int b) {
return a + b;
}
}
AdderFactory.java
package org.example;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.*;
import java.util.Arrays;
public class AdderFactory {
static {
// 将 cglib 生成的类保存到当前目录下
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, ".");
}
private static final MethodInterceptor methodInterceptor =
(obj, method, args, proxy) -> {
if (method.getName().equals("add")) {
String message = String.format("The arguments for the add method are: %s",
Arrays.toString(args));
System.out.println(message);
}
return proxy.invokeSuper(obj, args);
};
public static Adder buildAdder() {
return (Adder) Enhancer.create(Adder.class, methodInterceptor);
}
}
AdderFactoryTest.java
package org.example;
import net.sf.cglib.proxy.MethodInterceptor;
import org.junit.Assert;
import org.junit.Test;
import java.lang.reflect.Field;
public class AdderFactoryTest {
@Test
public void testBuildAdder() {
Adder adder = AdderFactory.buildAdder();
int lowerBound = 1;
int upperBound = 3;
for (int a = lowerBound; a <= upperBound; a++) {
for (int b = lowerBound; b <= upperBound; b++) {
Assert.assertEquals(a + b, adder.add(a, b));
}
}
}
@Test
public void testMethodInterceptor() throws ReflectiveOperationException {
Adder adder = AdderFactory.buildAdder();
Field methodInterceptorField = AdderFactory.class.getDeclaredField("methodInterceptor");
methodInterceptorField.setAccessible(true);
MethodInterceptor methodInterceptor = (MethodInterceptor) methodInterceptorField.get(null);
Field cglibCallback0Field = adder.getClass().getDeclaredField("CGLIB$CALLBACK_0");
cglibCallback0Field.setAccessible(true);
MethodInterceptor cglibCallback0 = (MethodInterceptor) cglibCallback0Field.get(adder);
Assert.assertSame(cglibCallback0, methodInterceptor);
}
}
Adder/AdderFactory/AdderFactoryTest 的类图如下 ⬇️
运行
在项目顶层执行如下命令,可以运行单元测试
mvn clean test
运行后,会看到项目顶层多了 net 和 org 这两个目录。执行 tree net org 命令后,会看到如下结果 ⬇️
net
└── sf
└── cglib
├── core
│ └── MethodWrapper$MethodWrapperKey$$KeyFactoryByCGLIB$$d45e49f7.class
└── proxy
└── Enhancer$EnhancerKey$$KeyFactoryByCGLIB$$7fb24d72.class
org
└── example
├── Adder$$EnhancerByCGLIB$$82ff904.class
├── Adder$$EnhancerByCGLIB$$82ff904$$FastClassByCGLIB$$8d93dfdd.class
└── Adder$$FastClassByCGLIB$$2ea8c1e0.class
7 directories, 5 files
以下三个文件看起来和 Adder 直接相关
Adder$$EnhancerByCGLIB$$82ff904.classAdder$$EnhancerByCGLIB$$82ff904$$FastClassByCGLIB$$8d93dfdd.classAdder$$FastClassByCGLIB$$2ea8c1e0.class
我们在 Intellij IDEA (Community Edition) 可以看到这些 class 文件反编译的结果(但完整的结果比较长,这里就不展示了)。我们先看看 Adder$$EnhancerByCGLIB$$82ff904.class。Adder$$EnhancerByCGLIB$$82ff904 的类图如下 ⬇️
分析
您是否想过这个问题 ⬇️
中的 字段是 类型的,它为什么可以调用 的基类(即,)的方法呢?
我们先看看 Adder$$EnhancerByCGLIB$$82ff904 的 add(int, int) 方法的逻辑 ⬇️ (以下内容由 IntelliJ IDEA (Community Edition) 反编译 Adder$$EnhancerByCGLIB$$82ff904.class 而得到)
public final int add(int var1, int var2) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
Object var3 = var10000.intercept(this, CGLIB$add$0$Method, new Object[]{new Integer(var1), new Integer(var2)}, CGLIB$add$0$Proxy);
return var3 == null ? 0 : ((Number)var3).intValue();
} else {
return super.add(var1, var2);
}
}
它的逻辑涉及两个 if 语句块,我们分别来看
第一个 if 语句块
第一个 if 条件本质上是在判断 this.CGLIB$CALLBACK_0 是否为 null。我用单元测试验证了如下两个字段是同一个引用 ⬇️ (单元测试的完整代码上文已提供)
- 中的 字段
Adder$$EnhancerByCGLIB$$82ff904中的CGLIB$CALLBACK_0字段
所以,可以简单认为第一个 if 条件 不成立。
第二个 if 语句块
刚才已经通过单元测试验证了以下两个字段是同一个引用 ⬇️ 那么第二个 if 条件 成立
- 中的 字段
Adder$$EnhancerByCGLIB$$82ff904中的CGLIB$CALLBACK_0字段
第二个 if 语句块的内容如下
if (var10000 != null) {
Object var3 = var10000.intercept(this, CGLIB$add$0$Method, new Object[]{new Integer(var1), new Integer(var2)}, CGLIB$add$0$Proxy);
return var3 == null ? 0 : ((Number)var3).intValue();
} else {
return super.add(var1, var2);
}
既然 if 条件成立,那么实际执行的就是如下的逻辑 ⬇️ (为了方便阅读,我把代码格式调整了一下)
Object var3 = var10000.intercept(
this,
CGLIB$add$0$Method,
new Object[]{new Integer(var1), new Integer(var2)},
CGLIB$add$0$Proxy
);
return var3 == null ? 0 : ((Number)var3).intValue();
其中 var10000 和 中的 字段是同一个引用,那么 var10000.intercept(...) 其实就是在调用下图红框对应的那个方法
| 形参 | 实参 |
|---|---|
(即,代理类 Adder$$EnhancerByCGLIB$$82ff904 的一个实例) | |
CGLIB$add$0$Method (引用 中 方法的 对象) | |
由 和 组成的 Object 数组 | |
CGLIB$add$0$Proxy (它比较复杂,下文再细说) |
前面三个参数容易解释它们的作用 ⬇️
因为我们在用动态代理,所以需要当前对象的实例,当前方法的引用,当前方法的参数(这也就是前三个参数提供的内容)。只有最后一个参数比较复杂。
CGLIB$add$0$Proxy 是什么
Adder$$EnhancerByCGLIB$$82ff904.class 反编译的完整结果比较长,我们只看其中一部分内容 ⬇️ (本文不关心的内容都用 ... 表示)
package org.example;
...
public class Adder$$EnhancerByCGLIB$$82ff904 extends Adder implements Factory {
...
private MethodInterceptor CGLIB$CALLBACK_0;
...
private static final Method CGLIB$add$0$Method;
private static final MethodProxy CGLIB$add$0$Proxy;
...
static void CGLIB$STATICHOOK1() {
...
Class var0 = Class.forName("org.example.Adder$$EnhancerByCGLIB$$82ff904");
Class var1;
...
CGLIB$add$0$Method = ReflectUtils.findMethods(new String[]{"add", "(II)I"}, (var1 = Class.forName("org.example.Adder")).getDeclaredMethods())[0];
CGLIB$add$0$Proxy = MethodProxy.create(var1, var0, "(II)I", "add", "CGLIB$add$0");
}
final int CGLIB$add$0(int var1, int var2) {
return super.add(var1, var2);
}
public final int add(int var1, int var2) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
Object var3 = var10000.intercept(this, CGLIB$add$0$Method, new Object[]{new Integer(var1), new Integer(var2)}, CGLIB$add$0$Proxy);
return var3 == null ? 0 : ((Number)var3).intValue();
} else {
return super.add(var1, var2);
}
}
...
static {
CGLIB$STATICHOOK1();
}
}
可以简单将 CGLIB$add$0$Proxy 的赋值逻辑简化如下 ⬇️
CGLIB$add$0$Proxy = MethodProxy.create(
Class.forName("org.example.Adder"),
Class.forName("org.example.Adder$$EnhancerByCGLIB$$82ff904"),
"(II)I",
"add",
"CGLIB$add$0"
);
CGLIB$add$0$Proxy 的类型是 ,和 add(int, int) 方法对应的 的简要类图如下 ⬇️(类图中只画了本文关心的内容)
invoke(Object obj, Object[] args) 方法
当我们调用这个 的 invoke(Object obj, Object[] args) 方法时,它会通过某种方式(细节要到下一篇文章再展开了)执行 obj.add(((Number)args[0]).intValue(), ((Number)args[1]).intValue()),而 obj 是动态代理类 Adder$$EnhancerByCGLIB$$82ff904 的实例,那么这里就会出现一个递归的调用
invokeSuper(Object obj, Object[] args) 方法
当我们调用这个 的 invokeSuper(Object obj, Object[] args) 方法时,它会通过某种方式(细节要到下一篇文章再展开了)执行 obj.CGLIB$add$0(((Number)args[0]).intValue(), ((Number)args[1]).intValue()),而 obj 是动态代理类 Adder$$EnhancerByCGLIB$$82ff904 的实例。Adder$$EnhancerByCGLIB$$82ff904 中的 CGLIB$add$0(int, int) 方法是这样的 ⬇️
final int CGLIB$add$0(int var1, int var2) {
return super.add(var1, var2);
}
Adder$$EnhancerByCGLIB$$82ff904 的基类是 。这样就可以调用 中的 add(int, int) 方法了。
其他
我把使用 PlantUML 插件绘制本文中若干图片的原始代码保存在 这篇笔记 里了