从另一个角度理解Java中的反射

从另一个角度理解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个步骤:

  1. 检查是否已经加载,有就直接返回,避免重复加载
  2. 当前缓存中确实没有该类,那么遵循双亲委派模型机制,加载.class文件
  3. 上面两步都失败了,调用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方法:在这里插入图片描述
反正,目前我们关于类加载只需知道以下信息:
在这里插入图片描述

Class类

发布了111 篇原创文章 · 获赞 0 · 访问量 2734

猜你喜欢

转载自blog.csdn.net/Cheng_MY/article/details/103070751