zoukankan      html  css  js  c++  java
  • JAVA反射原理

    什么是反射?

    反射,一种计算机处理方式。是程序可以访问、检测和修改它本身状态或行为的一种能力。java反射使得我们可以在程序运行时动态加载一个类,动态获取类的基本信息和定义的方法,构造函数,域等。除了检阅类信息外,还可以动态创建类的实例,执行类实例的方法,获取类实例的域值。反射使java这种静态语言有了动态的特性。

    类的加载

    java反射机制是围绕Class类展开的,在深入java反射原理之前,需要对类加载机制有一个大致的了解。jvm使用ClassLoader将字节码文件(class文件)加载到方法区内存中:

    Class clazz = ClassLoader.getSystemClassLoader().loadClass("com.mypackage.MyClass");
    

    可见ClassLoader根据类的完全限定名加载类并返回了一个Class对象,而java反射的所有起源都是从这个class类开始的。

    ReflectionData

    为了提高反射的性能,缓存显然是必须的。class类内部有一个useCaches静态变量来标记是否使用缓存,这个值可以通过外部配置项sun.reflect.noCaches进行开关。class类内部提供了一个ReflectionData内部类用来存放反射数据的缓存,并声明了一个reflectionData域,由于稍后进行按需延迟加载并缓存,所以这个域并没有指向一个实例化的ReflectionData对象。

        //标记是否使用缓存,可以通过外部配置项sun.reflect.noCaches进行禁用。
        private static boolean useCaches = true;
    
        static class ReflectionData<T> {
            volatile Field[] declaredFields;
            volatile Field[] publicFields;
            volatile Method[] declaredMethods;
            volatile Method[] publicMethods;
            volatile Constructor<T>[] declaredConstructors;
            volatile Constructor<T>[] publicConstructors;
            volatile Field[] declaredPublicFields;
            volatile Method[] declaredPublicMethods;
            final int redefinedCount;
    
            ReflectionData(int redefinedCount) {
                this.redefinedCount = redefinedCount;
            }
        }
    
        //注意这是个SoftReference,在内存资源紧张的时候可能会被回收。volatile保证多线程环境下的读写的正确性
        private volatile transient SoftReference<ReflectionData<T>> reflectionData;
        //J主要用于和ReflectionData中的redefinedCount进行比较,如果两个值不相等,说明ReflectionData缓存的数据已经过期了。
        private volatile transient int classRedefinedCount = 0;
    

    获取类的构造函数

    在获取一个类的class后,我们可以通过反射获取一个类的所有构造函数,class类内部封装了如下方法用于提取构造函数:

        //publicOnly指示是否只获取public的构造函数,我们常用的getConstructors方法是只返回public的构造函数,而getDeclaredConstructors返回的是所有构造函数,由于java的构造函数不会继承,所以这里不包含父类的构造函数。
        private Constructor<T>[] privateGetDeclaredConstructors(boolean publicOnly) {
            checkInitted(); //主要是读取了sun.reflect.noCaches配置。
            Constructor<T>[] res;
            ReflectionData<T> rd = reflectionData();//这里缓存中读取reflectionData,如果还没有缓存,则创建一个reflectionData并设置到缓存。但是注意这个ReflectionData可能只是个空对象,里面并没有任何数据。
            if (rd != null) {
                res = publicOnly ? rd.publicConstructors : rd.declaredConstructors;
                if (res != null) return res;//检查缓存中是否有数据
            }
            //没有缓存数据可用,从这里开始需要从jvm中去获取数据。
            if (isInterface()) {
                res = new Constructor[0];//接口没有构造函数
            } else {
                res = getDeclaredConstructors0(publicOnly);//native方法,从jvm中获取
            }
            //如果代码执行到了这里,说明需要更新缓存了,将之前从jvm中请求到的数据放置到缓存中。
            if (rd != null) {
                if (publicOnly) {
                    rd.publicConstructors = res;
                } else {
                    rd.declaredConstructors = res;
                }
            }
            return res;
        }
    

    上面的代码片段比较简单,主要就是读取配置,检查缓存中是否有有效数据,如果有,直接从缓存中返回,如果没有,调用native的方法从jvm中请求数据,然后设置到缓存。比较重要的是reflectionData()这个调用。这个方法主要是用于延迟创建并缓存ReflectionData对象,注意是对象,里面并没有保存反射数据,这些数据只有在第一次执行相应的反射操作后才会被填充。下面是这个方法的实现代码:

        private ReflectionData<T> reflectionData() {
            SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
            int classRedefinedCount = this.classRedefinedCount;
            ReflectionData<T> rd;
            //检查缓存是否有效,如果有效,从缓存中直接返回reflectionData。
            if (useCaches &&
                    reflectionData != null &&
                    (rd = reflectionData.get()) != null &&
                    rd.redefinedCount == classRedefinedCount) {
                return rd;
            }
            //无法使用到缓存,创建新的reflectionData
            return newReflectionData(reflectionData, classRedefinedCount);
        }
    

    下面看一下newReflectionData()的实现:

        private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData,int classRedefinedCount) {
            if (!useCaches) return null;
            //这里使用while-cas模式更新reflectionData,主要是考虑多线程并发更新的问题,可能有另外一个线程已经更新了reflectionData,并且设置了有效的缓存数据,如果这里再次更新就把缓存数据覆盖了。
            while (true) {
                ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);
                if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {
                    return rd;//cas成功,那么我们swap的这个新对象是有效的。
                }
                //重新读取oldReflectionData,为下次重试cas做准备。
                oldReflectionData = this.reflectionData;
                classRedefinedCount = this.classRedefinedCount;
                //先判断这个oldReflectionData是否有效。如果是无效的数据,需要去重试cas一个新的ReflectionData了。
                if (oldReflectionData != null &&
                        (rd = oldReflectionData.get()) != null &&
                        rd.redefinedCount == classRedefinedCount) {
                    return rd;//reflectionData中已经是有效的缓存数据了,直接返回这个reflectionData
                }
            }
        }
    

    上面的几个代码片段是反射获取一个类的构造函数的主要方法调用。主要流程就是先从class内部的reflectionData缓存中读取数据,如果没有缓存数据,那么就从jvm中去请求数据,然后设置到缓存中,供下次使用。

    执行构造函数

    在通过反射获取到一个类的构造函数我们,一般我们会试图通过构造函数去实例化声明这个构造函数的类,如下:

    MyClass myClass = (MyClass) constructor.newInstance();
    

    下面来看看这个newInstance()到底发生了什么:

        public T newInstance(Object... initargs)
                throws InstantiationException, IllegalAccessException,
                IllegalArgumentException, InvocationTargetException {
            //首先判断语言级别的访问检查是否被覆盖了(通过setAccess(true)方法可以将private的成员变成public),如果没有被覆盖,需要进行访问权限检查。
            if (!override) {
                if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                    Class<?> caller = Reflection.getCallerClass();
                    checkAccess(caller, clazz, null, modifiers);
                }
            }
            //枚举无法通过反射创建实例
            if ((clazz.getModifiers() & Modifier.ENUM) != 0)
                throw new IllegalArgumentException("Cannot reflectively create enum objects");
            //创建ConstructorAccessor对象,并进行缓存
            ConstructorAccessor ca = constructorAccessor;
            if (ca == null) {
                ca = acquireConstructorAccessor();
            }
        //通过ConstructorAccessor来执行newInstance
            return (T) ca.newInstance(initargs);
        }
    

    上面代码主要就是做权限检查,如果权限通过,则通过执行acquireConstructorAccessor()获取一个ConstructorAccessor来真正执行newInstance,acquireConstructorAccessor()这个方法实现比较简单,真正工作的是ReflectionFactory.newConstructorAccessor(),这个方法的主要工作就是为一个Constructor动态生成针对ConstructorAccessor接口的实现ConstructorAccessorImpl,下面来看一下这个方法实现:

        public ConstructorAccessor newConstructorAccessor(Constructor c) {
            checkInitted();
            Class declaringClass = c.getDeclaringClass();
            //如果声明这个构造函数的类部是抽象类,则返回一个InstantiationExceptionConstructorAccessorImpl,这其实也是个ConstructorAccessor,只是它对newInstace的实现是直接抛出一个InstantiationException异常
            if (Modifier.isAbstract(declaringClass.getModifiers())) {
                return new InstantiationExceptionConstructorAccessorImpl(null);
            }
            //如果是Class,同上
            if (declaringClass == Class.class) {
                return new InstantiationExceptionConstructorAccessorImpl
                        ("Can not instantiate java.lang.Class");
            }
            //如果是ConstructorAccessorImpl子类,这里会造成无限循环,所以直接通过native方式实例化这个类
            if (Reflection.isSubclassOf(declaringClass,
                    ConstructorAccessorImpl.class)) {
                return new BootstrapConstructorAccessorImpl(c);
            }
            //判断是否启用了Inflation机制,默认是启用了。
            //如果没有启用Inflation机制,那么通过asm操作字节码的方式来生成一个ConstructorAccessorImpl类。
            //如果启用了,那么在执行inflationThreshold(默认15次)次数之前,是通过navite调用来执行newInstance,超过这个次数之后,才会通过asm来生成类。
            //Inflation机制主要是在执行时间和启动时间上做一个平衡,native方式执行慢但是第一次执行不耗费任何时间,asm生成代码的方式执行快(20倍),但是第一次生成需要耗费大量的时间。
            //可以通过sun.reflect.noInflation和sun.reflect.inflationThreshold配置型来进行动态配置
            if (noInflation) {
                return new MethodAccessorGenerator().
                        generateConstructor(c.getDeclaringClass(),
                                c.getParameterTypes(),
                                c.getExceptionTypes(),
                                c.getModifiers());
            } else {
                NativeConstructorAccessorImpl acc =
                        new NativeConstructorAccessorImpl(c);
                DelegatingConstructorAccessorImpl res =
                        new DelegatingConstructorAccessorImpl(acc);
                acc.setParent(res);
                return res;
            }
        }
    

    由上面代码可见,在生成ConstructorAccessorImpl的操作上,一共提供多种版本的实现:直接抛出异常的实现,native执行的实现,asm生成代码的实现。native的实现比较简单,asm 版本的主要就是字节码操作了,比较复杂,暂时不做深入了。

  • 相关阅读:
    一点技巧
    题解G
    WA七次,疯了》》》》》OTZ
    就是过不了啊无奈。。。。。水题都过不了…………OTZ OTZ OTZ
    [IOS]使用UIScrollView和UIPageControl显示半透明帮助蒙板
    [System]几种同步方式
    [Objective C] Singleton类的一个模版
    [IOS] 自定义AlertView实现模态对话框
    [IOS] UIKit Animation
    [IOS]使用genstrings和NSLocalizedString实现App文本的本地化
  • 原文地址:https://www.cnblogs.com/techspace/p/6931397.html
Copyright © 2011-2022 走看看