背景
使用反射,我们可以调用一个指定类 中的任意方法。但是使用反射调用一个方法时,有一定的开销,是否有其他办法既能调用(几乎)任意的方法,又不至于让调用开销明显增大呢?cglib 中提供了 ,我们使用它就可以调用指定类 中的任意非 private 方法。
要点
中的部分代码如下 ⬇️ (这里只列出了本文关心的两个方法)
abstract public class FastClass {
...
/**
* 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.
*/
abstract public int getIndex(String name, Class[] parameterTypes);
...
/**
* 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
*/
abstract public Object invoke(int index, Object obj, Object[] args) throws InvocationTargetException;
...
}
为各个非 private 方法分配编号
里有 getIndex(String name, Class[] parameterTypes) 方法,它的入参作用如下
name:the method name方法名parameterTypes:the parameter array对应的入参类型的数组
这个方法可以给指定类 中定义的各个非 private 方法分配 int 类型的编号。
就本文中的例子而言,addV1(int, int)/addV2(int, int) 这两个方法分配的编号分别是 和 ⬇️
使用编号
里有 invoke(int index, Object obj, Object[] args) 方法,它会用到这个编号
index:the method index方法编号obj:the object the underlying method is invoked fromargs:the arguments used for the method call参数组成的数组
就本文的例子而言, 的子类中 invoke(int, Object, Object[]) 方法逻辑如下 ⬇️
正文
说明:我写代码时,得到了 TRAE 的协助。
下载相关 jar 包
本文的例子会用到 cglib,为了使代码短小,我会采用 java --class-path ... 的方式来运行 main 方法。既然不用 maven 管理依赖,我们就自行下载相关 jar 包。可以通过以下两个链接分别下载 3.3.0 版本的 cglib 和 7.1 版本的 asm (cglib 依赖了 asm)
下载后,将这两个 jar 包移动到合适的目录下,马上就会用到它们。
代码
请将以下代码保存为 Main.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 a = 1;
int b = 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[]{a, b});
System.out.printf("%d + %d = %s%n", a, b, 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, %d%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 . 应该会看到以下内容
.
├── asm-7.1.jar
├── cglib-3.3.0.jar
└── Main.java
1 directory, 3 files
编译和运行
编译
执行以下命令就可以编译 Main.java
javac --class-path cglib-3.3.0.jar Main.java
编译之后,会看到当前目录下多了 Main.class 和 Adder.class 两个文件。Main 和 Adder 的类图如下 ⬇️
运行
执行以下命令就可以运行 Main.java 中的 main 方法
java --add-opens=java.base/java.lang=ALL-UNNAMED --class-path cglib-3.3.0.jar:asm-7.1.jar:. Main
运行结果如下
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[]) 方法会用到这个编号
方法和编号的对应关系如下表所示 ⬇️
| 方法 | 编号 |
|---|---|
| addV1(int, int) | |
| addV2(int, int) | |
| addV3(int, int) | |
| equals(Object) | |
| toString() | |
| hashCode() |
我们看看 getIndex(String var1, Class[] var2)/invoke(int var1, Object var2, Object[] var3) 这两个方法的逻辑 ⬇️ (为了节约篇幅,其他方法都用 ... 代替了)
//
// 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");
}
...
}
getIndex(String var1, Class[] var2) 方法
getIndex(String var1, Class[] var2) 方法入参的作用如下
var1是方法名var2是那个方法的参数类型组成的数组
它会给 中定义的各个非 private 方法分配编号。这个方法来自基类 。基类中的 getIndex(String name, Class[] parameterTypes) 方法是这样的 ⬇️
/**
* 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.
*/
abstract public int getIndex(String name, Class[] parameterTypes);
invoke(int var1, Object var2, Object[] var3) 方法
invoke(int var1, Object var2, Object[] var3) 方法入参的作用如下
var1: 方法编号var2: 会被cast成 类型var3: 参数组成的数组
这个方法先根据 var1 的值,判定应当调用 中的哪个方法,然后直接执行对应的方法(不会借助反射)。我们来看看本文的例子对应的 invoke(int var1, Object var2, Object[] var3) 方法长什么样 ⬇️
这个方法来自基类 。基类中的 invoke(int index, Object obj, Object[] args) 方法是这样的 ⬇️
/**
* 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
*/
abstract public Object invoke(int index, Object obj, Object[] args) throws InvocationTargetException;
其他
文中的一些图是借助 PlantUML 的插件而生成的。画这些图所用到的原始代码列举如下
画 "Main 和 Adder 的类图" 用到的原始代码
@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
画 "Adder3c2f0ee 的类图" 用到的原始代码
@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