反射必杀技:深入了解Class类,让你一通百通
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了反射必杀技:深入了解Class类,让你一通百通相关的知识,希望对你有一定的参考价值。
1. Class 类的原理
孟子曰:得人心者得天下。而在 Java 中,这个「人心」就是?Class 类,获取到?Class?类我们就可以为所欲为之为所欲为。下面让我们深入「人心」,去探索 Class 类的原理。
首先了解 JVM 如何构建实例。
1.1 JVM 构建实例
JVM:Java Virtual Machine,Java 虚拟机。在?JVM?中分为栈、堆、方法区等,但这些都是?JVM?内存,文中所描述的内存指的就是?JVM?内存。.class?文件是字节码文件,是通过?.java?文件编译得来的。
知道上面这些内容,我们开始创建实例。我们以创建 Person 对象举例:
Person?p?=?new?Person()
简简单单通过?new?就创建了对象,那流程是什么样的呢?见下图
这也太粗糙了一些,那在精致一下吧。
同志们发现没有,其实这里还是有些区别的,我告诉你区别是下面的字比上面多,你会打我不(别打我脸)。
粗糙的那个是通过?new?创建的对象,而精致的是通过?ClassLoader?操作 .class?文件生成?Class?类,然后创建的对象。
其实通过?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?文件主要分位三个步骤
- 检查类是否已经加载,如果有就直接返回
- 当前不存在该类,遵循双亲委派机制,加载 .class?文件
- 上面两步都失败,调用 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 的对象会有下面的信息:
- 权限修饰符
- 类名和泛型信息
- 接口
- 实体
- 注解
- 构造函数
- 方法
这些信息在 .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?对象。那类加载器是如何加载的呢?
- 通过?ClassLoader?的?loadClass()?方法
- 从缓存中查找,直接返回
- 缓存中不存在,通过双亲委派机制加载
- 上面两步都失败,调用?findClass()
- 通过 IO 流从指定位置获取到 .class?文件得到字节数组
- 调用类加载器?defineClass() 方法,由字节数组得到?Class?对象
Class?类的构造器是私有的,所以需要通过?JVM?获取?Class。
Class.forName()?也是通过类加载器获取的?Class?对象。newInstance?方法的底层也是返回的无参构造函数。
以上是关于反射必杀技:深入了解Class类,让你一通百通的主要内容,如果未能解决你的问题,请参考以下文章