美文网首页一些收藏
JAVA反射-3(性能)

JAVA反射-3(性能)

作者: 小胖学编程 | 来源:发表于2020-11-04 18:51 被阅读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也很重要,反射性能在不断提高。

    相关文章

      网友评论

        本文标题:JAVA反射-3(性能)

        本文链接:https://www.haomeiwen.com/subject/rpagvktx.html