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的源码了解到了它的执行过程:
在类的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对象。尽量少重复生成。