java的反射调用比起直接调用要慢很多倍。而反射调用又是被广泛使用的。如何才能提高反射调用的速度呢?
一种想法就是对于每一个bean动态生成一个类,由这个类根据不同的方法名进行直接的方法调用。这样就把反射调用转换成了直接调用,速度应该有很大提高。而动态生成类虽然比较慢,但是在服务器运行期间只运行一次,这个代价还是值得的。
首先需要定义一个IBeanWrapper。这个wrapper负责进行反射调用到直接调用的转换。每个bean类会有一个自己的wrapper类实现。
public interface IBeanWrapper {
public void setValue(Object obj, String field, Object value);
public Object getValue(Object obj, String field);
}
然后是BeanWrapperFactory,负责获取wrapper的类实例。
public class BeanWrapperFactory {
private static Map<Class, IBeanWrapper> wrappers = new HashedMap();
public static IBeanWrapper getBeanWrapper(Class clz) {
IBeanWrapper inst = wrappers.get(clz);
if (inst == null) {
File outFile = new File(System.getProperties("java.io.temp"), "wrappers");
if (!outFile.exists()) {
outFile.mkdirs();
}
String s = new BeanWrapperFactory().getCode(UserEx.class);
CompilerUtil.compile(outFile.getAbsolutePath(), clz.getSimpleName()+"Wrapper", s);
DynamicClassLoader cl = new DynamicClassLoader(outFile);
try {
inst = (IBeanWrapper)cl.loadClass("com.temp.wrapper."+clz.getSimpleName()+"Wrapper").newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
wrappers.put(clz, inst);
}
return inst;
}
public String getCode(Class clz) {
StringBuffer sb = new StringBuffer();
sb.append("package com.temp.wrapper;\n");
sb.append("public class "+clz.getSimpleName()+"Wrapper implements IBeanWrapper {\n");
sb.append("public void setValue(Object obj, String field, Object value) {\n");
sb.append(" "+clz.getName()+" o = ("+clz.getName()+")obj;\n");
sb.append(" switch(field.hashCode()) {\n");
List<Field> fields = ReflectionUtils.getDeclaredFields(clz);
for (int i = 0 ; i < fields.size() ; i++) {
Field field = fields.get(i);
String name = field.getName();
String methodName = StringUtils.capitalise(name);
String type = getType(field.getType());
if ("id".equals(name)) {
try {
type = getType(clz.getMethod("getId").getReturnType());
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
}
sb.append(" case ").append(Integer.toString(name.hashCode())).append(":\n");
if (field.getType() == int.class) {
sb.append(" o.set").append(methodName).append("(((Number)value).intValue());\n");
} else if (field.getType() == long.class) {
sb.append(" o.set").append(methodName).append("(((Number)value).longValue());\n");
} else if (field.getType() == double.class) {
sb.append(" o.set").append(methodName).append("(((Number)value).doubleValue());\n");
} else if (field.getType() == byte.class) {
sb.append(" o.set").append(methodName).append("(((Number)value).byteValue());\n");
} else if (field.getType() == float.class) {
sb.append(" o.set").append(methodName).append("(((Number)value).floatValue());\n");
} else if (field.getType() == boolean.class) {
sb.append(" o.set").append(methodName).append("(((Boolean)value).booleanValue());\n");
} else if (field.getType() == char.class) {
sb.append(" o.set").append(methodName).append("(((Character)value).charValue());\n");
} else if (field.getType() == byte[].class) {
sb.append(" o.set").append(methodName).append("((byte[])value);\n");
} else {
sb.append(" o.set").append(methodName).append("((").append(type).append(")value);\n");
}
sb.append(" break;\n");
}
sb.append(" }\n");
sb.append("}\n\n");
sb.append("public Object getValue(Object obj, String field) {\n");
sb.append(" "+clz.getName()+" o = ("+clz.getName()+")obj;\n");
sb.append(" switch(field.hashCode()) {\n");
for (int i = 0 ; i < fields.size() ; i++) {
Field field = fields.get(i);
String name = field.getName();
String methodName = StringUtils.capitalise(name);
sb.append(" case ").append(Integer.toString(name.hashCode())).append(":\n");
if (field.getType() == boolean.class) {
sb.append(" return o.is").append(methodName).append("();\n");
} else {
sb.append(" return o.get").append(methodName).append("();\n");
}
}
sb.append(" }\n");
sb.append(" throw new RuntimeException(\"field not found:\"+field);\n");
sb.append("}");
sb.append("}");
return sb.toString();
}
private String getType(Class clz) {
if (clz == boolean.class) {
return "boolean";
} else if (clz == int.class) {
return "int";
} else if (clz == long.class) {
return "long";
} else if (clz == double.class) {
return "double";
} else if (clz == byte.class) {
return "byte";
} else if (clz == float.class) {
return "float";
} else {
return clz.getName();
}
}
}
其中静态方法
public static IBeanWrapper getBeanWrapper(Class clz)
负责获取一个给定类的wrapper实例。getCode()方法负责生成wrapper类的源代码。然后CompilerUtil进行编译,然后动态装载。静态变量wrappers用于缓存类对应的wrapper实例。为了提高速度,生成代码中没使用属性名字符串比较,而是直接用属性名的hash值比较。
DynamicClassLoader用于动态装载类。
public class DynamicClassLoader extends URLClassLoader { public DynamicClassLoader(File... files)//, ClassLoader parent) { super(getUrls(files));//, parent); } public DynamicClassLoader(ClassLoader parent, File... files) { super(getUrls(files), parent); } private static final URL[] getUrls(File... files) { URL[] urls = new URL[files.length]; try{ int i = 0; for(File file : files) { urls[i] = file.toURI().toURL(); } }catch(Exception ex){ } return urls; } }
动态编译类CompilerUtil可以参考点击打开链接
下面写段程序测试一下性能:
输出为:IBeanWrapper inst = new BeanWrapperFactory().getBeanWrapper(UserEx.class); User u = new User(); Method method = User.class.getMethod("setUserCode", String.class); long t0 = System.currentTimeMillis(); for (int i = 0 ; i < 100000000 ; i++) { u.setUserCode("123"); } long t1 = System.currentTimeMillis(); for (int i = 0 ; i < 100000000 ; i++) { inst.setValue(u, "userCode", "123"); } long t2 = System.currentTimeMillis(); for (int i = 0 ; i < 100000000 ; i++) { try { method.invoke(u, "123"); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } long t3 = System.currentTimeMillis(); System.out.println("time:"+(t1 - t0)+", "+(t2 - t1)+", "+(t3 - t2));
time:396, 3480, 15499
即快速反射调用大约是直接调用的9倍,是反射调用的1/5。提高还是很大的。这还是Method对象被缓存的情况下。如果不缓存Method对象,
IBeanWrapper inst = new BeanWrapperFactory().getBeanWrapper(UserEx.class); UserEx u = new UserEx(); Double d = new Double(123D); long t0 = System.currentTimeMillis(); for (int i = 0 ; i < 100000000 ; i++) { u.setD5(d); } long t1 = System.currentTimeMillis(); for (int i = 0 ; i < 100000000 ; i++) { inst.setValue(u, "userCode", "123"); } long t2 = System.currentTimeMillis(); for (int i = 0 ; i < 100000000 ; i++) { try { Method method = UserEx.class.getMethod("setUserCode", String.class); method.invoke(u, "123"); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } long t3 = System.currentTimeMillis(); System.out.println("time:"+(t1 - t0)+", "+(t2 - t1)+", "+(t3 - t2));
输出为
time:314, 3777, 246019
快速反射调用大约是反射调用的1/62。