深入理解系列之JAVA反射机制

版权声明:本文为博主原创文章,转载请注明出处和原始链接。 https://blog.csdn.net/u011552404/article/details/79970382

反射是指,程序运行期间,对于任意一个类,都能够知道这个类的所有属性和方法,且都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

问题一、反射机制的理论基础是什么?

如前言所属,基本每个java程序员都知道反射的概念和作用,但是为什么java可以支持反射?为什么C/C++就没有呢?其实,java之所以能够实现反射其根本的理论基础就在于JVM虚拟机中类文件、类加载这两个重要的特性!
1、类文件是Java支持反射的根本原因:
java虽然也是编译型语言语言,但是实际上在执行的过程中是采用的是“解释执行”,这是因为java的编译阶段只是把源代码编译成了一个“中间文件”——我们称之为Class文件(二进制文件存储),Class文件除了存储与源文件对应的“二进制”信息外,还存储了在解释执行期间的加载过程必备的与内存关联的信息,如属性、方法该加载到内存区域的哪个地方,以何种数据结构存储等等,这些所有的信息被称为“元信息”。存储在硬盘上的Class文件是反射能够实现的重要源头基础,但是反射本身并不是直接读取Class文件来获取类信息的,这是因为反射是在运行过程中从内存中获取的,所以实现反射的直接基础就是类加载过程!
2、类加载是反射实现的直接因素:
类加载过程包含加载-链接(验证-准备-解析)-初始化-使用-卸载五个大阶段,其中第一个阶段是反射能够实现的最直接的因素。在加载过程中,JVM主要做了以下三个工作:
①通过一个类的权限定名来获取定义此类的二进制流;
②将这个字节流代表的静态数据结构转化为运行时数据结构;
③在内存区生成一个java.lang.Class对象,作为方法区这个类的各种数据的访问入口;
我们看到第三个步骤中,在JVM虚拟机中生成了一个被称之为Class对象的内存数据,正是因为加载过程把Class文件信息转化为内存中Class对象信息,所以程序在运行期间可以从内存中读取任意一个类的所有信息,这也就是反射能够实现的直接原因所在!
我引入网上的一张图,来阐明类加载过程在内存中的体现:

这里写图片描述
3、动态链接阶段是反射发挥巨大应用的重要因素:
反射最大的用途并不仅仅就是获取这些信息并执行,而是联合类加载过程中“链接”阶段实现对象、方法、属性的动态绑定和生成,如我们常见的JDK动态代理就是反射+链接作为理论基础的!这是因为“链接”阶段是在程序运行期间动态执行的,所以我们当然可以在代码运行期间生成新的类并建立新的链接关系!
4、C/C++理论上也可以实现反射:
在C++中,通过RTTI(运行时类型识别),我们也可以知道类的一些信息,但为什么C++中却没有 Reflection,原因是类型信息不完整。RTTI这个名字本身就告诉我们,C++的类型信息是用来进行类型识别的,因此,它也不需要其它额外的信息。并不是C++无法做到这一点,而是C++不希望给用户增加额外的负担。因此,C++放弃了元对象。关于这一点,C++之父 Bjarne Stroustrup在他的《C++语言的设计与演化》的14.2.8节中进行了深入的讨论。而且正如上文叙述,仅仅通过反射获取一些信息并不是我们的终极目的,java的最大好处是可以通过反射+动态链接实现新对象、方法、属性的生成和调用,但是C++作为一种在编译阶段就已经实现了各个对象之间的连接关系的语言,是无法轻而易举的实现在运行过程中动态绑定这一特性的!

问题二、反射是如何通过代码实现的?

这个问题的意义就在于,程序员本身直接接触的是java代码,而JVM虚拟机最终打交道其实C/C++以及与之相关的系统调用,那么这个桥梁是如何建立的呢?这个问题当然得由源码来解答!
在看源码之前,我们有必要写几个例子来“温习一下”反射是怎么用的!

public class Reflect {

  public static void main(String[] args) throws Exception {
    //获取Class对象
    Class clazz = Class.forName("Student");
    //获取构造函数
    Constructor constructors = clazz.getConstructor();
    //生成对象
    Object obj = constructors.newInstance();
    //获取成员变量
    Field name = clazz.getDeclaredField("name");
    //修改成员变量
    name.setAccessible(true);
    name.set(obj, "叉叉叉");
    //获取方法
    Method method = clazz.getDeclaredMethod("run");
    //执行方法
    method.invoke(obj);
    System.out.println(clazz.getName()+" "+constructors.getName()+" "
            +obj.toString()+" "+name.getName()+" "+method.getName());
  }
}


class Student {
  private String name = "XXXX";
  public Student(){

  }

  public void run(){
    System.out.println("This is "+name);
  }
}

运行的结果如下:
This is 叉叉叉
Student Student Student@677327b6 name run
接着,我们着重选择Method对象来分析是如何实现反射的:
1、Method的获取原理:

@CallerSensitive
    public Field getDeclaredField(String name)
        throws NoSuchFieldException, SecurityException {
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        Field field = searchFields(privateGetDeclaredFields(false), name);
        if (field == null) {
            throw new NoSuchFieldException(name);
        }
        return field;
    }

核心语句就这一句:
Field field = searchFields(privateGetDeclaredFields(false), name);
其中privateGetDeclaredMethods方法的目的从缓存或JVM中获取该Class已经持有的方法列表,searchMethods方法将从返回的方法列表里通过循环来匹配方法名称进而找到一个与之匹配的方法对象。

private Field[] privateGetDeclaredFields(boolean publicOnly) {
        checkInitted();
        Field[] res;
        ReflectionData<T> rd = reflectionData();
        if (rd != null) {
            res = publicOnly ? rd.declaredPublicFields : rd.declaredFields;
            if (res != null) return res;
        }
        // No cached value available; request value from VM
        res = Reflection.filterFields(this, getDeclaredFields0(publicOnly));
        if (rd != null) {
            if (publicOnly) {
                rd.declaredPublicFields = res;
            } else {
                rd.declaredFields = res;
            }
        }
        return res;
    }

我们可以看到,在privateGetDeclaredMethods中首先由reflectionData()获取元数据,如果获取的数据不为空则说明缓存中有数据,则直接取出即可;否则就要从虚拟机中获取,即向JVM内存中请求元数据!从JVM中获得方法列表,接下来就开始匹配方法名称和参数进行提取特定的方法:

private static Field searchFields(Field[] fields, String name) {
        String internedName = name.intern();
        for (int i = 0; i < fields.length; i++) {
            if (fields[i].getName() == internedName) {
                return getReflectionFactory().copyField(fields[i]);
            }
        }
        return null;
    }

如果找到一个匹配的Method,则重新copy一份返回,即getReflectionFactory().copyField(fields[i]);所次每次调用getDeclaredMethod方法返回的Method对象其实都是经过一个新的对象!如果调用的较为频繁,建议把Method对象缓存起来。
2、Method的执行原理
方法获取后,还有invoke方法:

@CallerSensitive
    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        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);
    }

应该注意到,实际执行invoke的是methodAccessor类型的对象,实际上methodAccessor的初始化值为空,需要调用acquireMethodAccessor生成一个新的MethodAccessor对象,MethodAccessor本身就是一个接口,实现如下

public interface MethodAccessor {
  Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}

在acquireMethodAccessor方法中,会通过ReflectionFactory类的newMethodAccessor创建一个实现了MethodAccessor接口的对象,实现如下:

private MethodAccessor acquireMethodAccessor() {
        // First check to see if one has been created yet, and take it
        // if so
        MethodAccessor tmp = null;
        if (root != null) tmp = root.getMethodAccessor();
        if (tmp != null) {
            methodAccessor = tmp;
        } else {
            // Otherwise fabricate one and propagate it up to the root
            tmp = reflectionFactory.newMethodAccessor(this);
            setMethodAccessor(tmp);
        }

        return tmp;
    }

如果依然需要更为详细的源码解读,请参考深入分析Java方法反射的实现原理,同时可以根据自己的需要独立探究源码实现,总之反射的源码实现还是比较复杂的,期间必须涉及Native方法的调用来执行相关的动作

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页