1、ReflectASM 简介
1.1 什么是反射?
一般情况下,我们使用某个类时已经知道它是什么类了,用来做什么的,所以我们直接将这个类 进行实例化,然后使用这个类对象进行操作
反射就是在运行的过程中才知道要操作的类是什么。Java 反射实在运行的过程中,对任意一个类,都可以知道这个类的所有属性和方法,对一个对象,都能够调用任意一个方法和属性,这种动态获取的信息和调用对象方法的功能称为 java 的反射机制。
我们可以使用java.lang.Class
类,这个为了实现反射体用的一个类,通过全类名获取到反射的 Class 对象,然后通过 class 对象实现调用它的方法、获取属性、获取类信息等操作
1.2 什么是 reflectASM?
ASM 可以用来进行字节码操作,可以修改现有类或者以二进制形式动态生成类,ReflectASM 使用了 ASM,使用字节码生成的方式实现了更高效的反射机制。执行时会生成一个存取类来 set/get 字段,访问方法或者创建实例。不是依赖于 Java 本身的反射机制实现的,所以更快,而且避免了访问原始类型因自动装箱而产生的问题
ReflectASM 是一个非常小的 Java 类库,只有五个类,却提供了非常高性能的属性操作、方法调用、构造方法调用等,
2、maven 依赖
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>reflectasm</artifactId>
<version>1.11.0</version>
</dependency>
reflectASM 提供了根据匹配的字符串操作变量、函数的特性,reflectASM 中常用常用的类只有 MethodAccess,FieldAccess,Constructor 这几个
3、反射调用测试
反射对象
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PlanBarCodeExcel {
private String barCode;
public String brandName;
private String packingNum;
private String channelCode;
}
reflectASM 使用
public class ReflectASMDemo {
/**
* jdk 反射调用方法
* @throws Exception
*/
public static void jdkReflect() throws Exception {
PlanBarCodeExcel planBarCodeExcel = new PlanBarCodeExcel();
Method setBarCode = planBarCodeExcel.getClass().getMethod("setBarCode", String.class);
setBarCode.invoke(planBarCodeExcel, "barCode");
}
/**
* reflectASM反射调用方法
* 需要注意的是,reflectASM 提供的方法只能调用非私有的属性和方法,私有属性需要通过 get/set 方法调用
* @throws Exception
*/
public static void reflectASMByName() throws Exception {
// 使用 MethodAccess 反射调用方法
PlanBarCodeExcel planBarCodeExcel = new PlanBarCodeExcel();
MethodAccess access = MethodAccess.get(PlanBarCode.class);
methodAccess.invoke(planBarCodeExcel, "setBarCode", "barCode");
String getBarCode = (String) methodAccess.invoke(planBarCodeExcel, "getBarCode");
System.out.println(getBarCode);
// 如果方法重载有同名方法的话,找到方法的索引执行方法,相比通过名称访问成员,索引的方式会更快
int setChannelCodeIndex = methodAccess.getIndex("setChannelCode", String.class);
methodAccess.invoke(planBarCodeExcel, setChannelCodeIndex, "111");
// 使用 FieldAccess 反射 set/get 字段
FieldAccess fieldAccess = FieldAccess.get(PlanBarCode.class);
fieldAccess.set(planBarCodeExcel, "brandName", "brandName");
String brandName = (String) fieldAccess.get(planBarCodeExcel, "brandName");
System.out.println(brandName);
// ConstructorAccess反射调用构造方法
ConstructorAccess<PlanBarCodeExcel> planBarCodeExcelConstructorAccess = ConstructorAccess.get(PlanBarCodeExcel.class);
PlanBarCodeExcel planBarCodeExcel1 = planBarCodeExcelConstructorAccess.newInstance();
}
}
4、reflectASM 原理解析
4.1 MethodAccess 源码
public abstract class MethodAccess {
// 这三个私有变量用来存储要反射 Class 的方法名称、参数、返回值等信息
private String[] methodNames;
private Class[][] parameterTypes;
private Class[] returnTypes;
static public MethodAccess get (Class type) {
// 准备反射信息
ArrayList<Method> methods = new ArrayList<Method>();
boolean isInterface = type.isInterface();
if (!isInterface) { // 判断是否为接口
Class nextClass = type;
while (nextClass != Object.class) {
// 类中的公共非静态方法添加到 methods 中
addDeclaredMethodsToList(nextClass, methods);
nextClass = nextClass.getSuperclass();
}
} else {
recursiveAddInterfaceMethodsToList(type, methods);
}
int n = methods.size();
String[] methodNames = new String[n];
Class[][] parameterTypes = new Class[n][];
Class[] returnTypes = new Class[n];
for (int i = 0; i < n; i++) {
Method method = methods.get(i);
methodNames[i] = method.getName();
parameterTypes[i] = method.getParameterTypes();
returnTypes[i] = method.getReturnType();
}
String className = type.getName();
// 类名进行拼接,准备要生成新类的名字
String accessClassName = className + "MethodAccess";
if (accessClassName.startsWith("java.")) accessClassName = "reflectasm." + accessClassName;
Class accessClass;
// 获取该类的类加载器
AccessClassLoader loader = AccessClassLoader.get(type);
synchronized (loader) {
try {
// 如果动态生成的类已经生成过了,第二次调用是不会操作字节码生成的
accessClass = loader.loadClass(accessClassName);
} catch (ClassNotFoundException ignored) {
String accessClassNameInternal = accessClassName.replace('.', '/');
String classNameInternal = className.replace('.', '/');
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
MethodVisitor mv;
// 生成继承于 “com/esotericsoftware/reflectasm/MethodAccess” 类的子类
// 参数分别为:java Version、类修饰符、类名、参数类型、父类、接口
cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, "com/esotericsoftware/reflectasm/MethodAccess",null);
// 下面这段代码生成构造方法
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode(); // 开始生成方法
mv.visitVarInsn(ALOAD, 0);// 访问局部变量指令,加载或存储局部变量值的指令,从局部变量表 load 位置为 0 的变量到操作数栈
mv.visitMethodInsn(INVOKESPECIAL, "com/esotericsoftware/reflectasm/MethodAccess", "<init>", "()V"); // 访问方法时的指令
mv.visitInsn(RETURN); // 访问一个字节码指令
mv.visitMaxs(0, 0); // 方法的局部变量表和操作数栈的大小
mv.visitEnd(); // 方法访问结束
}
{
// 动态生成方法,公共的可变参的 invoke 方法
mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "invoke",
"(Ljava/lang/Object;I[Ljava/lang/Object;)Ljava/lang/Object;", null, null);
mv.visitCode();
if (!methods.isEmpty()) {
mv.visitVarInsn(ALOAD, 1);
mv.visitTypeInsn(CHECKCAST, classNameInternal);
mv.visitVarInsn(ASTORE, 4);
mv.visitVarInsn(ILOAD, 2);
Label[] labels = new Label[n];
for (int i = 0; i < n; i++)
labels[i] = new Label();
Label defaultLabel = new Label();
// label 代表的是跳转的字节码位置,对应一条指令
mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels);
StringBuilder buffer = new StringBuilder(128);
for (int i = 0; i < n; i++) { // n = methods.size();
// 跳转到指定字节码位置
mv.visitLabel(labels[i]);
if (i == 0)
mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] {classNameInternal}, 0, null);
else
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitVarInsn(ALOAD, 4);
buffer.setLength(0);
buffer.append('(');
Class[] paramTypes = parameterTypes[i];
Class returnType = returnTypes[i];
for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
mv.visitVarInsn(ALOAD, 3);
mv.visitIntInsn(BIPUSH, paramIndex);
mv.visitInsn(AALOAD);
// 获取到参数类型
Type paramType = Type.getType(paramTypes[paramIndex]);
// 根据参数类型的 sort 使用 switch 判断是否为基本数据类型,避免基本数据类型的自动装箱操作,避免创建不必要的对象
switch (paramType.getSort()) {
case Type.BOOLEAN:
mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z");
break;
case Type.BYTE:
mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B");
break;
case Type.CHAR:
mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C");
break;
case Type.SHORT:
mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S");
break;
case Type.INT:
mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
break;
case Type.FLOAT:
mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F");
break;
case Type.LONG:
mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J");
break;
case Type.DOUBLE:
mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D");
break;
case Type.ARRAY:
mv.visitTypeInsn(CHECKCAST, paramType.getDescriptor());
break;
case Type.OBJECT:
mv.visitTypeInsn(CHECKCAST, paramType.getInternalName());
break;
}
// 拼接参数类型描述
buffer.append(paramType.getDescriptor());
}
buffer.append(')');
// 拼接返回参数类型描述
buffer.append(Type.getDescriptor(returnType));
int invoke;
if (isInterface)
invoke = INVOKEINTERFACE; // 调用接口方法
else if (Modifier.isStatic(methods.get(i).getModifiers()))
// 调用静态方法
invoke = INVOKESTATIC;
else
// 调用普通方法
invoke = INVOKEVIRTUAL;
// 对字节码进行注入
mv.visitMethodInsn(invoke, classNameInternal, methodNames[i], buffer.toString());
// 判断返回值与参数同操作
switch (Type.getType(returnType).getSort()) {
case Type.VOID:
mv.visitInsn(ACONST_NULL);
break;
case Type.BOOLEAN:
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;");
break;
case Type.BYTE:
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;");
break;
case Type.CHAR:
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;");
break;
case Type.SHORT:
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;");
break;
case Type.INT:
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");
break;
case Type.FLOAT:
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;");
break;
case Type.LONG:
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;");
break;
case Type.DOUBLE:
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;");
break;
}
mv.visitInsn(ARETURN);
}
mv.visitLabel(defaultLabel);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
// 下面是一些新建和调用的操作
mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException");
mv.visitInsn(DUP);
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitLdcInsn("Method not found: ");
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn(ILOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(ATHROW);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
// method 没有其它调用了,结束创建过程
cw.visitEnd();
// 字节码生成,使用 ClassWriter 的 toByteArray() 方法,返回我们需要的代表这个类的 byte 数组
byte[] data = cw.toByteArray();
// 我们将这个 class 文件进行加载
accessClass = loader.defineClass(accessClassName, data);
}
}
try {
// 实例化并赋值返回结果
MethodAccess access = (MethodAccess)accessClass.newInstance();
access.methodNames = methodNames;
access.parameterTypes = parameterTypes;
access.returnTypes = returnTypes;
return access;
} catch (Throwable t) {
throw new RuntimeException("Error constructing method access class: " + accessClassName, t);
}
}
// 使用反射获取到要反射类的公有非静态方法后赋值给私有变量进行存储
private static void addDeclaredMethodsToList (Class type, ArrayList<Method> methods) {
Method[] declaredMethods = type.getDeclaredMethods();
for (int i = 0, n = declaredMethods.length; i < n; i++) {
Method method = declaredMethods[i];
int modifiers = method.getModifiers();
// 判断是否为私有方法
if (Modifier.isPrivate(modifiers)) continue;
methods.add(method);
}
}
private static void recursiveAddInterfaceMethodsToList (Class interfaceType, ArrayList<Method> methods) {
addDeclaredMethodsToList(interfaceType, methods);
for (Class nextInterface : interfaceType.getInterfaces()) {
recursiveAddInterfaceMethodsToList(nextInterface, methods);
}
}
}
4.2 读源码后 了解到 MethodAccess 执行流程
- 通过 Java 反射获取 class 的公共非静态函数名、属性等
- 在私有变量内记录方法名称参数等信息
- 用 ASM 技术动态生成新的继承于 MethodAccess 的类(fileName + MethodAccess)
- 在类的 invoke 方法实现调用不同的方法
4.3 小结
-
反射的核心是 getMethod 和 invoke,reflectASM 要生成文件然后加载到 JVM,所以 reflectASM 的 get 特别慢,
-
使用 jar 包要注意缓存生成的 class 对象,尽量少重复生成
-
reflectASM 借助反射的 getDeclaredMethods 获取 class 的所有方法,然后动态生成一个继承于 MethodAccess 的子类 ,动态生成一个 class 文件并加载到 jvm 中。这个操作比较耗时,我们最好预先生成这个 MethodAccess 对象,将这个生成的对象缓存起来
-
子类中所有的方法名建立 index 索引,index 和方法名映射,根据方法名获取到 index,子类中的 switch 执行相应的代码,在 MethodAccess.invoke 的时候,是直接调用
-
如果反射调用的类函数比较多,这个遍历也可能耗时较多,可以先通过 getIndex 方法先获取到索引,然后再进行调用,这样比使用函数的名字来进行调用快的多