从另一个角度理解Java中的反射
主要内容:
JVM是如何构建一个实例的
.class文件
类加载器
Class类
反射API
JVM是如何构建一个实例的
假设main方法中有以下代码:
Person p = new Person();
会经历以下这个过程:
通过new创建实例和反射创建实例,都绕不开Class对象。
.class文件
有人用编辑器打开.class文件看过吗?
比如我现在写一个类
用vim命令打开.class文件,以16进制显示就是下面这副鬼样子:
在计算机中,任何东西底层保存的形式都是0101代码。
.java源码是给人类读的,而.class字节码是给计算机读的。根据不同的解读规则,可以产生不同的意思。就好比“这周日你有空吗”,合适的断句很重要。
同样的,JVM对.class文件也有一套自己的读取规则,不需要我们操心。总之,0101代码在它眼里的样子,和我们眼中的英文源码是一样的。
类加载器
在最开始复习对象创建过程时,我们了解到.class文件是由类加载器加载的。此处可以先看 类加载器那篇文章。但是核心方法只有loadClass(),告诉它需要加载的类名,它会帮你加载:
loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 首先,检查是否已经加载该类
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果尚未加载,则遵循父优先的等级加载机制(所谓双亲委派机制)
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) {
// 模板方法模式:如果还是没有加载成功,调用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文件大致可以分为3个步骤:
- 检查是否已经加载,有就直接返回,避免重复加载
- 当前缓存中确实没有该类,那么遵循双亲委派模型机制,加载.class文件
- 上面两步都失败了,调用findClass()方法,自己去加载
需要注意的是,ClassLoader类本身是抽象类,而抽象类是无法通过new创建对象的。所以它的findClass()方法写的很随意,直接抛了异常,反正你无法通过ClassLoader对象调用。也就是说,父类ClassLoader中的findClass()方法根本不会去加载.class文件。
正确的做法是,子类重写覆盖findClass(),在里面写自定义的加载逻辑。比如:
public Class<?> gt; findClass(String name) throws ClassNotFoundException {
try {
/*自己另外写一个getClassData()
通过IO流从指定位置读取xxx.class文件得到字节数组*/
byte[] datas = getClassData(name);
if(datas == null) {
throw new ClassNotFoundException("类没有找到:" + name);
}
//调用类加载器本身的defineClass()方法,由字节码得到Class对象
return defineClass(name, datas, 0, datas.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException("类找不到:" + name);
}
}
defineClass()是ClassLoader定义的方法,目的是根据.class文件的字节数组byte[] b造出一个对应的Class对象。我们无法得知具体是如何实现的,因为最终它会调用一个native方法:
反正,目前我们关于类加载只需知道以下信息: