反射从入门到精通之深刻了解Class类

知其然,知其因此然

0. 前言

本文会讲解反射的原理,若是你们对反射不了解,能够先看《反射从0到入门》,对反射有大概的了解。java

《反射从入门到精通》我会分为两篇来说解,这一篇是讲解 Class 类的原理,下一篇我会讲解反射 API 的原理。vim

1. Class 类的原理

孟子曰:得人心者得天下。而在 Java 中,这个「人心」就是 Class 类,获取到 Class 类咱们就能够随心所欲之随心所欲。下面让咱们深刻「人心」,去探索 Class 类的原理。数组

首先了解 JVM 如何构建实例。缓存

1.1 JVM 构建实例

JVM:Java Virtual Machine,Java 虚拟机。在 JVM 中分为栈、堆、方法区等,但这些都是 JVM 内存,文中所描述的内存指的就是 JVM 内存。.class 文件是字节码文件,是经过 .java 文件编译得来的。ide

知道上面这些内容,咱们开始建立实例。咱们以建立 Person 对象举例:函数

Person p = new Person()

简简单单经过 new 就建立了对象,那流程是什么样的呢?见下图this

这也太粗糙了一些,那在精致一下吧。spa

同志们发现没有,其实这里仍是有些区别的,我告诉你区别是下面的字比上面多,你会打我不(别打我脸)。3d

粗糙的那个是经过 new 建立的对象,而精致的是经过 ClassLoader 操做 .class 文件生成 Class 类,而后建立的对象。code

其实经过 new 或者反射建立实例,都须要 Class 对象。

1.2 .class 文件

.class 文件在文章开头讲过,是字节码文件。.java 是源程序。Java 程序是跨平台的,一次编译处处执行,而编译就是从源文件转换成字节码文件。

字节码无非就是由 0 和 1 构成的文件。

有以下一个类:

经过 vim 查看一下字节码文件:

这啥玩意,看不懂。咱也不须要看懂,反正 JVM.class 文件有它本身的读取规则。

1.3 类加载器

还记得上面的精致图中,咱们知道是经过类加载器把 .class 文件加载到内存中。具体的类加载器内容,我会另写一篇文章讲解(写完连接会更新到这里)。可是核心方法就是 loadClass(),只须要告诉它要加载的 name,它就会帮你加载:

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 1.检查类是否已经加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // 2.还没有加载,遵循父优先的等级加载机制(双亲委派机制)
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // 3.若是尚未加载成功,调用 findClass()
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

// 须要重写该方法,默认就是抛出异常
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

类加载器加载 .class 文件主要分位三个步骤

  1. 检查类是否已经加载,若是有就直接返回
  2. 当前不存在该类,遵循双亲委派机制,加载 .class 文件
  3. 上面两步都失败,调用 findClass()

由于 ClassLoader 的 findClass 方法默认抛出异常,须要咱们写一个子类从新覆盖它,好比:

@Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            // 经过IO流从指定位置读取xxx.class文件获得字节数组
            byte[] datas = getClassData(name);
            if (null == datas){
                throw new ClassNotFoundException("类没有找到:" + name);
            }
            // 调用类加载器自己的defineClass()方法,由字节码获得 class 对象
            return defineClass(name, datas, 0, datas.length);
        }catch (IOException e){
            throw new ClassNotFoundException("类没有找到:" + name);
        }
    }

    private byte[] getClassData(String name) {
        return byte[] datas;
    }

defineClass 是经过字节码获取 Class 的方法,是 ClassLoader 定义的。咱们具体不知道如何实现的,由于最终会调用一个 native 方法:

private native Class<?> defineClass0(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd);

    private native Class<?> defineClass1(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd, String source);

    private native Class<?> defineClass2(String name, java.nio.ByteBuffer b,
                                         int off, int len, ProtectionDomain pd,
                                         String source);

总结下类加载器加载 .class 文件的步骤:

  • 经过 ClassLoader 类中 loadClass() 方法获取 Class

    • 从缓存中查找,直接返回
    • 缓存中不存在,经过双亲委派机制加载
    • 上面两步都失败,调用 findClass()

      • 经过 IO 流从指定位置获取到 .class 文件获得字节数组
      • 调用类加载器 defineClass() 方法,由字节数组获得 Class 对象

1.4 Class 类

.class 文件已经被类加载器加载到内存中并生成字节数组,JVM 根据字节数组建立了对应的 Class 对象。

接下来咱们来分析下 Class 对象。

咱们知道 Java 的对象会有下面的信息:

  1. 权限修饰符
  2. 类名和泛型信息
  3. 接口
  4. 实体
  5. 注解
  6. 构造函数
  7. 方法

这些信息在 .class 文件以 0101 表示,最后 JVM 会把 .class 文件的信息经过它的方式保存到 Class 中。

Class 中确定有保存这些信息的字段,咱们来看一下:

Class 类中用 ReflectionData 里面的字段来与 .class 的内容映射,分别映射了字段、方法、构造器和接口。

经过 annotaionData 映射了注解数据,其它的就不展现了,你们能够自行打开 IDEA 查看下 Class 的源码。

那咱们看看 Class 类的方法

1.4.1 构造器

Class 类的构造器是私有的,只能经过 JVM 建立 Class 对象。因此就有了上面经过类加载器获取 Class 对象的过程。

1.4.2 Class.forName

Class.forName() 方法仍是经过类加载器获取 Class 对象。

1.4.3 newInstance

newInstance() 的底层是返回无参构造函数。

2. 总结

咱们来梳理下前面的知识点:

反射的关键点就是获取 Class 类,那系统是如何获取到 Class 类?

是经过类加载器 ClassLoader.class 文件经过字节数组的方式加载到 JVM 中,JVM 将字节数组转换成 Class 对象。那类加载器是如何加载的呢?

  • 经过 ClassLoaderloadClass() 方法

    • 从缓存中查找,直接返回
    • 缓存中不存在,经过双亲委派机制加载
    • 上面两步都失败,调用 findClass()

      • 经过 IO 流从指定位置获取到 .class 文件获得字节数组
      • 调用类加载器 defineClass() 方法,由字节数组获得 Class 对象

Class 类的构造器是私有的,因此须要经过 JVM 获取 Class

Class.forName() 也是经过类加载器获取的 Class 对象。newInstance 方法的底层也是返回的无参构造函数。


相关文章
相关标签/搜索