背景
使用反射,我们可以调用一个类 <math xmlns="http://www.w3.org/1998/Math/MathML"> C \text{C} </math>C 中的任意方法。但是使用反射调用一个方法时,有一定的开销,是否有其他办法既能调用(几乎)任意的方法,又不至于让调用开销明显增大呢?cglib 中提供了 <math xmlns="http://www.w3.org/1998/Math/MathML"> net.sf.cglib.reflect.FastClass \text{net.sf.cglib.reflect.FastClass} </math>net.sf.cglib.reflect.FastClass,使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> net.sf.cglib.reflect.FastClass \text{net.sf.cglib.reflect.FastClass} </math>net.sf.cglib.reflect.FastClass 我们可以调用指定类 <math xmlns="http://www.w3.org/1998/Math/MathML"> C \text{C} </math>C 中的任意非 private 方法。
要点
为各个非 private 方法分配编号
<math xmlns="http://www.w3.org/1998/Math/MathML"> net.sf.cglib.reflect.FastClass \text{net.sf.cglib.reflect.FastClass} </math>net.sf.cglib.reflect.FastClass 会给指定类 <math xmlns="http://www.w3.org/1998/Math/MathML"> C \text{C} </math>C 中定义的各个非 private 方法分配编号。 <math xmlns="http://www.w3.org/1998/Math/MathML"> net.sf.cglib.reflect.FastClass \text{net.sf.cglib.reflect.FastClass} </math>net.sf.cglib.reflect.FastClass 里有 getIndex(String, Class[]) 方法,它会给各个非 private 方法分配 int 类型的编号。
就本文中的例子而言,addV1(int, int)/addV2(int, int) 这两个方法分配的编号分别是 <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 0 </math>0 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1 ⬇️ 
使用编号
<math xmlns="http://www.w3.org/1998/Math/MathML"> net.sf.cglib.reflect.FastClass \text{net.sf.cglib.reflect.FastClass} </math>net.sf.cglib.reflect.FastClass 里有 invoke(int var1, Object var2, Object[] var3) 方法,它会用到这个编号
var1: 方法编号var2: 会被cast成 <math xmlns="http://www.w3.org/1998/Math/MathML"> C \text{C} </math>C 类型var3: 参数组成的数组
就本文的例子而言, <math xmlns="http://www.w3.org/1998/Math/MathML"> net.sf.cglib.reflect.FastClass \text{net.sf.cglib.reflect.FastClass} </math>net.sf.cglib.reflect.FastClass 的子类中 invoke(int var1, Object var2, Object[] var3) 方法逻辑如下 ⬇️

正文
说明:我写代码时,得到了 TRAE 的协助。
下载相关 jar 包
本文的例子会用到 cglib,为了使代码短小,我会用采用 java --class-path ... 的方式来运行 main 方法。既然不用 maven 管理依赖,我们就自行下载相关 jar 包。可以通过以下两个链接分别下载 3.3.0 版本的 cglib 和 7.1 版本的 asm (cglib 依赖了 asm)


下载后,将这两个 jar 包移动到合适的目录下,马上就会用到它们。
代码
请将以下代码保存为 Main.java
java
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.reflect.FastClass;
public class Main {
static {
// 将 cglib 生成的类保存到当前目录下
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, ".");
}
public static void main(String[] args) throws InvocationTargetException {
// 创建 FastClass 实例,用于快速方法调用
FastClass fastClass = FastClass.create(Adder.class);
// 创建 Adder 实例
Adder adder = new Adder();
// 获取 Adder 类的所有声明方法
Method[] methods = Adder.class.getDeclaredMethods();
// 定义方法参数类型(两个 int 类型)
Class<?>[] parameterTypes = new Class[]{int.class, int.class};
// 定义测试参数值
int param1 = 1;
int param2 = 1;
// 遍历所有方法
for (Method method : methods) {
// 跳过 private 方法
if (Modifier.isPrivate(method.getModifiers())) {
continue;
}
// 打印分隔线,提高输出可读性
System.out.println("=".repeat(20));
// 获取方法名和 FastClass 中的索引
String methodName = method.getName();
int methodIndex = fastClass.getIndex(methodName, parameterTypes);
System.out.printf("Method index for %s: %d%n", methodName, methodIndex);
// 调用方法并获取结果
Object result = fastClass.invoke(methodIndex, adder, new Object[]{param1, param2});
System.out.printf("%d + %d = %s%n", param1, param2, result);
}
}
}
class Adder {
public int addV1(int a, int b) {
System.out.printf("addV1(int, int) is called with %d, %d%n", a, b);
return a + b;
}
protected int addV2(int a, int b) {
System.out.printf("addV2(int, int) is called with %d, %s%n", a, b);
return a + b;
}
int addV3(int a, int b) {
System.out.printf("addV3(int, int) is called with %d, %d%n", a, b);
return a + b;
}
private int addV4(int a, int b) {
System.out.printf("addV4(int, int) is called with %d, %d%n", a, b);
return a + b;
}
}
现在执行 tree . 应该会看到以下内容
text
.
├── asm-7.1.jar
├── cglib-3.3.0.jar
└── Main.java
1 directory, 3 files
编译和运行
编译
执行以下命令就可以编译 Main.java
bash
javac --class-path cglib-3.3.0.jar Main.java
编译之后,会看到当前目录下多了 Main.class 和 Adder.class 两个文件。Main 和 Adder 的类图如下 ⬇️

运行
执行以下命令就可以运行 Main.java 中的 main 方法
bash
java --add-opens=java.base/java.lang=ALL-UNNAMED --class-path cglib-3.3.0.jar:asm-7.1.jar:. Main
运行结果如下
text
CGLIB debugging enabled, writing to '.'
====================
Method index for addV1: 0
addV1(int, int) is called with 1, 1
1 + 1 = 2
====================
Method index for addV2: 1
addV2(int, int) is called with 1, 1
1 + 1 = 2
====================
Method index for addV3: 2
addV3(int, int) is called with 1, 1
1 + 1 = 2
可以看到当前目录下生成了 Adder$$FastClassByCGLIB$$3c2f0ee.class 文件。借助 IntelliJ IDEA (Community Edition) 可以查看这个 class 文件反编译后的内容(完整的内容比较长,这里就不展示了)。
结果分析
我给 Adder$$FastClassByCGLIB$$3c2f0ee 类画了简要的类图 ⬇️

getIndex(String, Class[]) 会给各个(不包含 private 级别的) add 方法分配编号。 invoke(int, Object, Object[]) 方法会用到这个编号
方法和编号的对应关系如下表所示 ⬇️
| 方法 | 编号 | 在 invoke(int var1, Object var2, Object[] var3) 方法中如何处理? |
|---|---|---|
| addV1(int, int) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 0 </math>0 | return new Integer(((Adder)var2).addV1(((Number)var3[0]).intValue(), ((Number)var3[1]).intValue())); |
| addV2(int, int) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1 | return new Integer(((Adder)var2).addV2(((Number)var3[0]).intValue(), ((Number)var3[1]).intValue())); |
| addV3(int, int) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 2 </math>2 | return new Integer(((Adder)var2).addV3(((Number)var3[0]).intValue(), ((Number)var3[1]).intValue())); |
| equals(Object) | <math xmlns="http://www.w3.org/1998/Math/MathML"> 3 3 </math>3 | return new Boolean(((Adder)var2).equals(var3[0])); |
| toString() | <math xmlns="http://www.w3.org/1998/Math/MathML"> 4 4 </math>4 | return ((Adder)var2).toString(); |
| hashCode() | <math xmlns="http://www.w3.org/1998/Math/MathML"> 5 5 </math>5 | return new Integer(((Adder)var2).hashCode()); |
我们看看 getIndex(String var1, Class[] var2)/invoke(int var1, Object var2, Object[] var3) 这两个方法的逻辑 ⬇️ (为了节约篇幅,其他方法都用 ... 代替了)
java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
import java.lang.reflect.InvocationTargetException;
import net.sf.cglib.core.Signature;
import net.sf.cglib.reflect.FastClass;
public class Adder$$FastClassByCGLIB$$3c2f0ee extends FastClass {
public Adder$$FastClassByCGLIB$$3c2f0ee(Class var1) {
super(var1);
}
...
public int getIndex(String var1, Class[] var2) {
switch (var1.hashCode()) {
case -1776922004:
if (var1.equals("toString")) {
switch (var2.length) {
case 0:
return 4;
}
}
break;
case -1295482945:
if (var1.equals("equals")) {
switch (var2.length) {
case 1:
if (var2[0].getName().equals("java.lang.Object")) {
return 3;
}
}
}
break;
case 92659452:
if (var1.equals("addV1")) {
switch (var2.length) {
case 2:
if (var2[0].getName().equals("int") && var2[1].getName().equals("int")) {
return 0;
}
}
}
break;
case 92659453:
if (var1.equals("addV2")) {
switch (var2.length) {
case 2:
if (var2[0].getName().equals("int") && var2[1].getName().equals("int")) {
return 1;
}
}
}
break;
case 92659454:
if (var1.equals("addV3")) {
switch (var2.length) {
case 2:
if (var2[0].getName().equals("int") && var2[1].getName().equals("int")) {
return 2;
}
}
}
break;
case 147696667:
if (var1.equals("hashCode")) {
switch (var2.length) {
case 0:
return 5;
}
}
}
return -1;
}
...
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
Adder var10000 = (Adder)var2;
int var10001 = var1;
try {
switch (var10001) {
case 0:
return new Integer(var10000.addV1(((Number)var3[0]).intValue(), ((Number)var3[1]).intValue()));
case 1:
return new Integer(var10000.addV2(((Number)var3[0]).intValue(), ((Number)var3[1]).intValue()));
case 2:
return new Integer(var10000.addV3(((Number)var3[0]).intValue(), ((Number)var3[1]).intValue()));
case 3:
return new Boolean(var10000.equals(var3[0]));
case 4:
return var10000.toString();
case 5:
return new Integer(var10000.hashCode());
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
...
}
其他
文中的一些图是借助 PlantUML 的插件而生成的。画这些图所用到的原始代码列举如下
画 "Main 和 Adder 的类图" 用到的原始代码
puml
@startuml
title <i>Main</i> 和 <i>Adder</i> 的类图
caption \n\n
' caption 中的内容是为了防止掘金平台生成的水印遮盖图中的文字
class Main {
+ {static} void main(String[])
}
class Adder {
+ int addV1(int,int)
# int addV2(int,int)
~ int addV3(int,int)
- int addV4(int,int)
}
@enduml
画 "Adder <math xmlns="http://www.w3.org/1998/Math/MathML"> F a s t C l a s s B y C G L I B FastClassByCGLIB </math>FastClassByCGLIB3c2f0ee 的类图" 用到的原始代码
puml
@startuml
'https://plantuml.com/class-diagram
title <i>Adder$$FastClassByCGLIB$$3c2f0ee</i> 的类图
caption 图中只画了本文关心的内容
abstract "net.sf.cglib.reflect.FastClass" as F
abstract class F {
+ {abstract} int getIndex(String name, Class[] parameterTypes)
+ {abstract} Object invoke(int index, Object obj, Object[] args) throws InvocationTargetException
}
class Adder$$FastClassByCGLIB$$3c2f0ee
F <|-- Adder$$FastClassByCGLIB$$3c2f0ee
class Adder$$FastClassByCGLIB$$3c2f0ee {
+ Adder$$FastClassByCGLIB$$3c2f0ee(Class var1)
+ int getIndex(String var1, Class[] var2)
+ Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException
}
note left of F::"getIndex(String name, Class[] parameterTypes)"
<code>
/**
* Return the index of the matching method. The index may be used
* later to invoke the method with less overhead. If more than one
* method matches (i.e. they differ by return type only), one is
* chosen arbitrarily.
* @see #invoke(int, Object, Object[])
* @param name the method name
* @param parameterTypes the parameter array
* @return the index, or <code>-1</code> if none is found.
*/
</code>
end note
note left of F::invoke
<code>
/**
* Invoke the method with the specified index.
* @see getIndex(name, Class[])
* @param index the method index
* @param obj the object the underlying method is invoked from
* @param args the arguments used for the method call
* @throws java.lang.reflect.InvocationTargetException if the underlying method throws an exception
*/
</code>
end note
@enduml