一些收藏

JAVA反射-3(性能)

2020-11-04  本文已影响0人  小胖学编程

Java反射-1(理论)
Java反射-2(技巧)

1. 善于使用api

1.1 使用合适的方法获取反射对象

使用反射时,例如尽量不要使用getMethods()等方法后再遍历筛选,而是直接使用getMethod(methodName)等方法根据方法名来获取方法。

1.2 取消安全检查

即使是调用public方法,也需要使用 field.setAccessible(true)来取消安全检查。

Java中通过反射执行一个方法的过程如下:

public class Foo {
    private void doStuff() {
        System.out.println("hello world");
    }
}

public class TestFoo {

    @Test
    public void test() throws Exception {
        String methodName = "doStuff";
        Method declaredMethod = Foo.class.getDeclaredMethod(methodName);
        //由开发者决定是否要逃避安全体系的检查(是否可以快速获取)
        System.out.println(declaredMethod.isAccessible());
        if(declaredMethod.isAccessible()){
            declaredMethod.setAccessible(true);
        }
        declaredMethod.invoke(new Foo());
    }
}

实际上,在通过反射执行方法时,必须在invoke之前检查Accessible属性。但是方法对象的Accessible属性并不是用来决定是否可以访问的。

public class Foo {

    public void doStuff() {
        System.out.println("hello world");
    }

    public static void main(String[] args) throws NoSuchMethodException {
        String methodName = "doStuff";
        Method declaredMethod = Foo.class.getDeclaredMethod(methodName);
        System.out.println(declaredMethod.isAccessible());
    }
}

实际上,即使反射的方法访问修饰符为public,isAccessible()的最终结果依旧是false。而且即使为false,还是可以使用invoke()方法执行。

故Accessible的属性并不是我们语法层次理解的访问权限,而是指是否更加容易获得,是否进行安全检查。

我们知道,动态修改一个类或者执行方法都会受到java安全体系的制约,而安全处理是非常消耗资源的(性能非常低),因此对于运行期要执行的方法或要修改的属性提供了Accessible可选项:由开发者来决定是否逃避安全体系的检查。

在源码java.lang.reflect.AccessibleObject#isAccessible中,该方法默认返回值为false。

AccessibleObject是Field、Method、Constructor的父类,决定其是否可以快速访问而不进行访问控制检查。在AccessibleObject中是以override变量保存该值的,但是具体是否快速执行是在Method类的invoke方法中决定的。

    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException{
        //检查是否可以快速获取,其值是父类的AccessibleObject的override变量
        if (!override) {
            //不能快速获取,要进行安全检查
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        //直接执行方法
        return ma.invoke(obj, args);
    }

Accessible属性只是用来判断是否需要进行安全检查的,如果不需要则直接执行,这样可以大幅度地提升系统性能(当然了,由于取消了安全检查,也可以运行private方法,获取private私有属性)。

2. 善于使用缓存

将反射得到的元数据保存起来,使用时,只需要从内存中获取即可。

元数据包含classfieldmethodconstructor

比如对class对象的缓存:

    private Map<String, Class> classMap = new HashMap<>();

    Class getClass(String className) throws ClassNotFoundException {
        Class<?> clazz = classMap.get(className);
        if (className == null) {
            clazz = Class.forName(className);
            classMap.put(className, clazz);
        }
        return clazz;
    }

使用内省时,也可以将PropertyDescriptor缓存起来,也可以提高效率。

public abstract class IntrospectorUtils {
    public static final ConcurrentHashMap<String, PropertyDescriptor> introspectorCache
            = new ConcurrentHashMap<>(16);
    //存储字段
    public static PropertyDescriptor persistencePropertyDescriptor(String name, Class clazz) throws IntrospectionException {
        if (StringUtils.isBlank(name) || clazz == null) {
            throw new BusinessException("业务参数有误,系统异常!");
        }
        String key = clazz.getSimpleName() + name;
        PropertyDescriptor propertyDescriptor = new PropertyDescriptor(name, clazz);
        //存在返回,不存在去填充(线程安全)
        return introspectorCache.computeIfAbsent(key, V -> propertyDescriptor);
    }
}

3. 善于使用框架

例如:copy对象时,可以使用CGLib的BeanCopier(其原理是运行时动态生成了用于复制某个类的字节码),其性能比反射框架org.springframework.beans.BeanUtils性能要高。

    /**
     * @param source      原始对象
     * @param targetClass 拷贝的对象类型
     */
    public static <T1, T2> T2 createCopy(T1 source, Class<T2> targetClass) {
        if (source == null) {
            throw new RuntimeException("参数异常");
        } else {
            T2 target;
            try {
                target = targetClass.newInstance();
            } catch (Exception e) {
                throw new RuntimeException(e);
            } 
            BeanCopier beanCopier = BeanCopier.create(source.getClass(), targetClass, false);
            beanCopier.copy(source, target, null);
            return target;
        }
    }

要提高Method.invoke性能,可以使用JDK7的MethodHandler(由于Method.invoke的JIT优化,差距不大)。使用高版本的JDK也很重要,反射性能在不断提高。

上一篇下一篇

猜你喜欢

热点阅读