反射必杀技:深入了解Class类,让你一通百通

开发 后端
孟子曰:得人心者得天下。而在 Java 中,这个「人心」就是 Class 类,获取到 Class 类我们就可以为所欲为之为所欲为。下面让我们深入「人心」,去探索 Class 类的原理。

1. Class 类的原理

孟子曰:得人心者得天下。而在 Java 中,这个「人心」就是 Class 类,获取到 Class 类我们就可以为所欲为之为所欲为。下面让我们深入「人心」,去探索 Class 类的原理。

[[287309]]

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

1.1 JVM 构建实例

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

知道上面这些内容,我们开始创建实例。我们以创建 Person 对象举例:

  1. Person p = new Person() 

简简单单通过 new 就创建了对象,那流程是什么样的呢?见下图: 

反射必杀技:深入了解Class类,让你一通百通

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

反射必杀技:深入了解Class类,让你一通百通

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

粗糙的那个是通过 new 创建的对象,而精致的是通过 ClassLoader 操作 .class 文件生成 Class 类,然后创建的对象。

其实通过 new 或者反射创建实例,都需要 Class 对象。

1.2 .class 文件

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

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

有如下一个类: 

反射必杀技:深入了解Class类,让你一通百通

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

反射必杀技:深入了解Class类,让你一通百通

这啥玩意,看不懂。咱也不需要看懂,反正 JVM 对 .class 文件有它自己的读取规则。

1.3 类加载器

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

  1. protected Class<?> loadClass(String name, boolean resolve) 
  2.     throws ClassNotFoundException 
  3.     synchronized (getClassLoadingLock(name)) { 
  4.         // 1.检查类是否已经加载 
  5.         Class<?> c = findLoadedClass(name); 
  6.         if (c == null) { 
  7.             long t0 = System.nanoTime(); 
  8.             try { 
  9.                 // 2.尚未加载,遵循父优先的等级加载机制(双亲委派机制) 
  10.                 if (parent != null) { 
  11.                     c = parent.loadClass(namefalse); 
  12.                 } else { 
  13.                     c = findBootstrapClassOrNull(name); 
  14.                 } 
  15.             } catch (ClassNotFoundException e) { 
  16.                 // ClassNotFoundException thrown if class not found 
  17.                 // from the non-null parent class loader 
  18.             } 
  19.  
  20.             if (c == null) { 
  21.                 // 3.如果还没有加载成功,调用 findClass() 
  22.                 long t1 = System.nanoTime(); 
  23.                 c = findClass(name); 
  24.  
  25.                 // this is the defining class loader; record the stats 
  26.                 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); 
  27.                 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 
  28.                 sun.misc.PerfCounter.getFindClasses().increment(); 
  29.             } 
  30.         } 
  31.         if (resolve) { 
  32.             resolveClass(c); 
  33.         } 
  34.         return c; 
  35.     } 
  36.  
  37. // 需要重写该方法,默认就是抛出异常 
  38. protected Class<?> findClass(String name) throws ClassNotFoundException { 
  39.     throw new ClassNotFoundException(name); 
  1. 类加载器加载 .class 文件主要分位三个步骤
  2. 检查类是否已经加载,如果有就直接返回
  3. 当前不存在该类,遵循双亲委派机制,加载 .class 文件

上面两步都失败,调用 findClass()

因为 ClassLoader 的 findClass 方法默认抛出异常,需要我们写一个子类重新覆盖它,比如:

  1. @Override 
  2.     protected Class<?> findClass(String name) throws ClassNotFoundException { 
  3.         try { 
  4.             // 通过IO流从指定位置读取xxx.class文件得到字节数组 
  5.             byte[] datas = getClassData(name); 
  6.             if (null == datas){ 
  7.                 throw new ClassNotFoundException("类没有找到:" + name); 
  8.             } 
  9.             // 调用类加载器本身的defineClass()方法,由字节码得到 class 对象 
  10.             return defineClass(name, datas, 0, datas.length); 
  11.         }catch (IOException e){ 
  12.             throw new ClassNotFoundException("类没有找到:" + name); 
  13.         } 
  14.     } 
  15.  
  16.     private byte[] getClassData(String name) { 
  17.         return byte[] datas; 
  18.     } 

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

  1. private native Class<?> defineClass0(String name, byte[] b, int offint len, 
  2.                                          ProtectionDomain pd); 
  3.  
  4.     private native Class<?> defineClass1(String name, byte[] b, int offint len, 
  5.                                          ProtectionDomain pd, String source); 
  6.  
  7.     private native Class<?> defineClass2(String name, java.nio.ByteBuffer b, 
  8.                                          int offint len, ProtectionDomain pd, 
  9.                                          String source); 

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

  • 通过 ClassLoader 类中 loadClass() 方法获取 Class
  • 从缓存中查找,直接返回
  • 缓存中不存在,通过双亲委派机制加载
  • 上面两步都失败,调用 findClass()通过 IO 流从指定位置获取到 .class 文件得到字节数组调用类加载器 defineClass() 方法,由字节数组得到 Class 对象

1.4 Class 类

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

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

反射必杀技:深入了解Class类,让你一通百通

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

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

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

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

反射必杀技:深入了解Class类,让你一通百通

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

反射必杀技:深入了解Class类,让你一通百通

通过 annotaionData 映射了注解数据,其它的就不展示了,大家可以自行打开 IDEA 查看下 Class 的源码。

那我们看看 Class 类的方法

1.4.1 构造器 

反射必杀技:深入了解Class类,让你一通百通

Class 类的构造器是私有的,只能通过 JVM 创建 Class 对象。所以就有了上面通过类加载器获取 Class 对象的过程。

1.4.2 Class.forName 

反射必杀技:深入了解Class类,让你一通百通

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

1.4.3 newInstance 

反射必杀技:深入了解Class类,让你一通百通

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

2. 总结

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

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

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

  • 通过 ClassLoader 的 loadClass() 方法
  • 从缓存中查找,直接返回
  • 缓存中不存在,通过双亲委派机制加载
  • 上面两步都失败,调用 findClass()通过 IO 流从指定位置获取到 .class 文件得到字节数组调用类加载器 defineClass() 方法,由字节数组得到 Class 对象

Class 类的构造器是私有的,所以需要通过 JVM 获取 Class。

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

 

责任编辑:武晓燕 来源: 今日头条
相关推荐

2023-07-18 06:48:03

2010-05-14 13:05:57

2014-04-22 10:50:31

统一通信UCBYOD

2010-08-24 14:57:33

外企职场

2011-06-27 14:56:49

SEO

2018-09-21 14:32:00

iPaas云应用部署

2024-08-09 12:11:07

2013-05-10 09:23:14

iPaaS混合云集成云集成

2010-01-06 17:18:54

Linux常用软件

2010-08-11 16:43:05

职场

2009-07-22 15:02:18

2021-02-02 10:55:09

等级保护2.0信息安全网络安全

2010-05-12 18:04:00

统一通信服务

2012-10-18 13:48:31

统一通信UC

2010-11-18 10:52:54

统一通信

2009-10-13 16:38:04

强行关闭VMware虚

2023-04-07 17:44:43

2011-06-24 17:23:30

网站优化

2023-04-10 11:18:24

GPT模型

2009-01-03 09:14:00

点赞
收藏

51CTO技术栈公众号