【雨夜】性能优化(反射调用)
序
最近在做公司的项目 性能优化。如果只考虑项目自身的性能,主要有以下2点
- 业务流程
- 代码 本身性能
这里主要说第二点,毕竟第一点要根据大家自己的业务场景
场景
我们一般使用反射,可能是 数据库的操作之前 需要给对象赋值(id / 创建时间 / 修改时间)
有的人说 我用的是
@Component
public class MybatisHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//属性名
this.setFieldValByName("createTime", new Date(), metaObject);
//this.setFieldValByName("createUser", SecureUtil.getUserId(), metaObject);
//不维护create_user可以不使用这行代码
}
@Override
public void updateFill(MetaObject metaObject) {
//属性名
this.setFieldValByName("updateTime", new Date(), metaObject);
//this.setFieldValByName("updateUser", SecureUtil.getUserId(), metaObject);
}
}
这不是官方提供的方法么,应该就是最优解,应该不会有比这个更好的了
private Object getBeanProperty(PropertyTokenizer prop, Object object) {
try {
Invoker method = this.metaClass.getGetInvoker(prop.getName());
try {
return method.invoke(object, NO_ARGUMENTS);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} catch (RuntimeException var6) {
throw var6;
} catch (Throwable var7) {
throw new ReflectionException("Could not get property '" + prop.getName() + "' from " + object.getClass() + ". Cause: " + var7.toString(), var7);
}
}
其中 根据 class 获取 方法 Methods
private Method[] getClassMethods(Class<?> cls) {
Map<String, Method> uniqueMethods = new HashMap();
for(Class<?> currentClass = cls; currentClass != null && currentClass != Object.class; currentClass = currentClass.getSuperclass()) {
this.addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
Class<?>[] interfaces = currentClass.getInterfaces();
Class[] var5 = interfaces;
int var6 = interfaces.length;
for(int var7 = 0; var7 < var6; ++var7) {
Class<?> anInterface = var5[var7];
this.addUniqueMethods(uniqueMethods, anInterface.getMethods());
}
}
Collection<Method> methods = uniqueMethods.values();
return (Method[])methods.toArray(new Method[methods.size()]);
}
method.invoke(object, params);
其中的method 是 通过 class 调用 getMethods 方法 进行获取的进行了 cache
缓存到了
private final Map<String, Invoker> getMethods = new HashMap();
虽然是进行了Mehtod的cache ,但是最后还是 反射那一套,和下面的reflect 是同一个
反射调用
正常的反射调用方法
package reflect;
import java.lang.reflect.Method;
public class GetMethod {
public static void main(String[] args) throws Exception{
String s1 = "Hello Java";
String t1 = s1.substring(6);
System.out.println(t1);
System.out.println("-------------测试通过反射调用方法-----------");
String s2 = "Hello world";
//getMethod 需要传入两个参数,一个是方法名,另一个是该方法名需要的参数类型,
//getMethod 的返回值是Method对象
//substring 有两个重载方法, substring(int begin)、substring(int begin, int end)
Method m1 = s2.getClass().getMethod("substring", int.class);
Method m2 = String.class.getMethod("substring", int.class, int.class);
// invoke 是Method对象中的一个调用方法的方法,返回值是 Object 类型,下面做了强制转换
// invoke 第一个参数是 对象实例, 后面跟着的是可变参数,参数类型要与上面getMethod方法对应
String t2 = (String)m1.invoke(s2, 6);
String t3 = (String)m2.invoke(s2, 3, 6);
System.out.println(t2);
System.out.println(t3);
}
}
大家都知道 反射的性能比较差,但是差到什么程度呢,差了几倍呢
上面的例子 对应着 reflect
参数讲解
direct 是直接调用 user.getName 方法的耗时,是一个标准
倒数第二行是反射的测试结果,相当于一次反射的调用 相当于6次的user.getName 调用
最后一列的Units 是 ns/op ,是一个操作消耗了多少纳秒
既然反射这么差,有什么办法优化么?
- jdk7的 MethodHandle
- jdk8的Lambda
- asm 方法
那一个一个说
jdk7的 MethodHandle
MethodHandle
它是可对直接执行的方法(或域、构造方法等)的类型的引用,或者说,它是一个有能力安全调用方法的对象。换个方法来说,通过句柄我们可以直接调用该句柄所引用的底层方法。从作用上来看,方法句柄类似于反射中的Method类,但是方法句柄的功能更加强大、使用更加灵活、性能也更好。
{
//定义方法的返回值和入参
MethodType mt = MethodType.methodType(String.class);
String methodName = buildGetterName(attr);
MethodHandle mh = null;
try {
//查找方法句柄
MethodHandle orginalMh = MethodHandles.lookup().findVirtual(target, methodName, mt);
//适配,mh调用的输出输出都是Object
mh = orginalMh.asType(MethodType.methodType(Object.class, Object.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new IllegalArgumentException(e);
}
return mh;
}
jdk8的Lambda
public class LambdaMetaTool implements ReflectTool {
private final Function getterFunction;
public LambdaMetaTool() {
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
CallSite site = LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class),
lookup.findVirtual(User.class, "getName", MethodType.methodType(String.class)),
MethodType.methodType(String.class, User.class));
getterFunction = (Function) ((CallSite) site).getTarget().invokeExact();
} catch (Throwable ex) {
throw new IllegalArgumentException(ex);
}
}
@Override
public Object getValue(Object target, String attr) {
return getterFunction.apply(target);
}
public static void main(String[] args) {
LambdaMetaTool tool = new LambdaMetaTool();
User user = new User();
user.setName("abc");
String value = (String) tool.getValue(user, "name");
System.out.println(value);
}
}
asm 方法
public class ReflectAsmTool implements ReflectTool {
MethodAccess methodAccess = null;
int index;
public ReflectAsmTool(Class target, String attr) {
methodAccess = MethodAccess.get(target);
String methodName = buildGetterName(attr);
index = methodAccess.getIndex(methodName);
}
@Override
public Object getValue(Object target, String attr) {
return methodAccess.invoke(target, index, attr);
}
}
上面3个方法都是和 原生的get方法差不多的耗时 但是asm 有一个问题,他只能操作 非private的 方法和属性,如果是private 还是要用反射去操作
代码地址
yuye-spring-boot-starter/README.md at main · yuyezhiji/yuye-spring-boot-starter (github.com)
ReflectAsmTool reflectAsmTool = new ReflectAsmTool(User.class, attr);
reflectAsmTool .getValue();
个人
请勿转载
转载自:https://juejin.cn/post/7268813574251085843