ReflectASM => Java 高性能反射

Posted 思想累积

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ReflectASM => Java 高性能反射相关的知识,希望对你有一定的参考价值。

1、ReflectASM 简介

1.1 什么是反射?

​ Java 反射实在运行的过程中,对任意一个类,都可以知道这个类的所有属性和方法,对一个对象,都能够调用任意一个方法和属性,这种动态获取的信息和调用对象方法的功能称为 java 的反射机制。

1.2 什么是 reflectASM?

​ ReflectASM 是一个非常小的 Java 类库,只有五个类,却提供了非常高性能的属性操作、方法调用、构造方法调用等,在底层使用了 asm。 使用字节码生成的方式实现了更为高效的反射机制,执行时会生成一个存取类来 set/get 字段,访问方法或者创建实例。不是依赖于 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 {
	private String[] methodNames;
	private Class[][] parameterTypes;
	private Class[] returnTypes;

    static public MethodAccess get (Class type) {
        // 准备反射信息
        // ToDo:为什么不先找动态生成的 MethodAccess 类,然后再准备反射信息?
		ArrayList<Method> methods = new ArrayList<Method>();
		boolean isInterface = type.isInterface();
		if (!isInterface) {
			Class nextClass = type;
			while (nextClass != Object.class) {
				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 {
                // 如果动态生成的类已经生成过了,第二次调用 MethodAccess.get 是不会操作字节码生成的
				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"
				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);
					mv.visitMethodInsn(INVOKESPECIAL, "com/esotericsoftware/reflectasm/MethodAccess", "<init>", "()V");
					mv.visitInsn(RETURN);
					mv.visitMaxs(0, 0);
					mv.visitEnd();
				}
				{
					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();
						mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels);

						StringBuilder buffer = new StringBuilder(128);
						for (int i = 0; i < n; i++) {
							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]);
								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&g

以上是关于ReflectASM => Java 高性能反射的主要内容,如果未能解决你的问题,请参考以下文章

Java各种反射性能对比

Java各种反射性能对比

项目工具:两行代码快速生成测试的数据的FakeDataMaker

Java 虚拟机原理Java 反射原理 ( 反射作用 | 反射用法 )

Java 反射性能

基于reflectasm打造自己的通用bean工具