【雨夜】性能优化(反射调用)

2,114 阅读3分钟

最近在做公司的项目 性能优化。如果只考虑项目自身的性能,主要有以下2点

  1. 业务流程
  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);
	}
}

这不是官方提供的方法么,应该就是最优解,应该不会有比这个更好的了

image.png

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);

    }
}


大家都知道 反射的性能比较差,但是差到什么程度呢,差了几倍呢

image.png

上面的例子 对应着 reflect

参数讲解

direct 是直接调用 user.getName 方法的耗时,是一个标准

倒数第二行是反射的测试结果,相当于一次反射的调用 相当于6次的user.getName 调用

最后一列的Units 是 ns/op ,是一个操作消耗了多少纳秒

既然反射这么差,有什么办法优化么?

image.png

  1. jdk7的 MethodHandle
  2. jdk8的Lambda
  3. 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();

个人

github.com/yuyezhiji

请勿转载