当前位置: 首页 > 工具软件 > ReflectASM > 使用案例 >

高效反射ReflectASM原理学习

阴靖
2023-12-01

ReflectAS

M原理学习

最近学习了静态代理、动态代理等技术,进而知道了ASM jar包,
知道了以此jar包为基础开发的一个高性能的反射包ReflectASM。
想起自己之前写代码的时候使用的都是java自带的反射包对性能有些许浪费,
而这jar包号称几乎直接在代码里调用方法一样的性能,顿时兴起学习之心。

ReflectASM有这么神奇吗?使用了反射还能那么高效?

打开jar包后发现其实就4个类而已,确实非常的小
看源码如果只是反射调用方法其实只用到了
AccessClassLader 和MethodAccess两个类

反射目标

package cn.lsg.codegenerate;

public class Demo {

    private String a;
    private Integer b;

    public String getA() {
        return a;
    }

    public Integer getB() {
        return b;
    }

    public void setA(String a) {
        this.a = a;
    }

    public void setB(Integer b) {
        this.b = b;
    }

}

main方法

 public static void main(String[] args) throws IOException {
        Demo d=new Demo();
        d.setA("asasa");
        d.setB(50);
        MethodAccess m = MethodAccess.get(Demo.class);
        Object invoke = m.invoke(d, "getA");
        System.out.println(invoke);
    }

读MethodAccess的源码了解到了它的执行过程:

  1. 反射Demo.class获取公共非静态方法
  2. 用ASM技术动态生成新的继承于MethodAccess的类DemoMethodAccess
  3. 在私有变量内记录方法名称参数等信息
  4. 在类的invoke方法内实现调用不同的方法

    我想看看 invoke 方法的内容,发现很难实现,起初想使用对象流将class
    对象保存下来,但发现class未实现序列化接口,断点调试也无法进入方法。
    后来想了个取巧的办法,直接建立一个新的类将MethodAccess内容复制出来,
    修改了其中的代码

    static public MethodAccess get (Class 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;
                //将继承类的路径换成新的(全部替换) 这里换成cn/lsg/codegenerate/MethodAccess
                cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, "cn/lsg/codegenerate/MethodAccess",
                        new String[]{"java/io/Serializable"});
             ......
                byte[] data = cw.toByteArray();
                //添加的代码
                FileOutputStream fout;
                try {
                    fout = new FileOutputStream("C:/Users/Administrator/Desktop/新建文件夹/"+accessClassName+".class");
                    fout.write(data);
                    fout.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                accessClass = loader.defineClass(accessClassName, data);
            }
            //添加的代码
    
        }
        try {
            MethodAccess access = (MethodAccess)accessClass.newInstance();
            access.methodNames = methodNames;
            access.parameterTypes = parameterTypes;
            access.returnTypes = returnTypes;
            return access;
        } catch (Exception ex) {
            throw new RuntimeException("Error constructing method access class: " + accessClassName, ex);
        }
    }

在返回对象前将存储字节码的byte数组输出到了文件里。
用反编译器反编译了生成的class文件 终于知道了实现方式

package cn.lsg.codegenerate;

import java.io.Serializable;

public class DemoMethodAccess extends MethodAccess
  implements Serializable
{
  public Object invoke(Object paramObject, int paramInt, Object[] paramArrayOfObject)
  {
    Demo localDemo = (Demo)paramObject;
    switch (paramInt)
    {
    case 0:
      localDemo.setA((String)paramArrayOfObject[0]);
      return null;
    case 1:
      localDemo.setB((Integer)paramArrayOfObject[0]);
      return null;
    case 2:
      return localDemo.getA();
    case 3:
      return localDemo.getB();
    }
    throw new IllegalArgumentException("Method not found: " + paramInt);
  }
}

原来如此,难怪性能能和直接调用媲美 这根本就是直接调用方法
但是在调用方法之前使用了反射、又生成了字节码还进行了新类的加载 这三部一样
是很耗时的。如果将从开始到结束的所有时间算进去 其实它相比其他反射并不怎么占优,但如果只生成一次代理类的话其性能就很高了。所以使用这个jar包要注意缓存
生成的class对象。尽量少重复生成。

 类似资料: