Java反射机制原理
从 Spring XML 配置的一个问题出发,系统整理反射的本质、类加载三阶段及获取 Class 对象的三种方式。**
一、从一个问题开始
学 Spring 的时候,你一定写过这样的 XML 配置:
<bean id="userDao" class="com.xq.dao.impl.UserDaoImpl"></bean>
Spring 启动之后,UserDaoImpl 的对象就有了,可以直接用。
Spring 框架的代码是别人写好编译好的,它在编译的时候根本不知道你的 com.xq.dao.impl.UserDaoImpl 存不存在。它不可能提前写好:
// Spring 源码里不可能有这行
UserDaoImpl userDao = new UserDaoImpl();
那它是怎么做到的?
在程序运行的时候,拿着 "com.xq.dao.impl.UserDaoImpl" 这个字符串,动态地找到这个类,然后创建出对象。
这就是反射。
二、反射的本质
反射(Reflection)是 Java 提供的一种能力:在程序运行时,动态获取任意类的信息,并对其进行操作。
这里的"类信息"包括:
- 成员变量(Field)
- 方法(Method)
- 构造器(Constructor)
- 父类、接口、注解……
正常情况下,我们在编译期就确定了要用哪个类、调哪个方法。而反射打破了这个限制——类名可以是运行时才知道的一个字符串,就像 XML 里 class 属性的值一样。
Spring、MyBatis 等框架之所以能做到"配置驱动",底层全部依赖反射。
三、类加载的三个阶段
一个 .java 文件,是怎么一步步变成内存里可以操作的对象的?
阶段一:磁盘阶段
.java 源文件经过 javac 编译,生成 .class 字节码文件存在磁盘上。此时类还没有进入内存。
阶段二:类对象阶段
当程序第一次用到某个类时,JVM 的类加载器(ClassLoader) 把对应的 .class 文件读进内存:
- 在方法区中存储这个类的所有信息(字段、方法、构造器……)
- 在堆中生成一个对应的
Class对象,作为访问方法区类信息的入口
这个 Class 对象,就是反射一切操作的起点。
阶段三:运行时阶段
通过 Class 对象,可以调用构造器,在堆里创建出这个类的具体实例——也就是我们平时 new 出来的对象。
.java 文件 →(javac)→ .class 文件 →(类加载器)→ Class 对象 →(反射/new)→ 实例对象
磁盘阶段 磁盘阶段 类对象阶段 运行时阶段
四、获取 Class 对象的三种方式
Java 提供了三种方式,分别对应类加载的不同阶段。
方式一:Class.forName("全类名")
Class<?> clazz = Class.forName("com.xq.dao.impl.UserDaoImpl");
对应磁盘阶段。 类名是一个运行时才确定的字符串,这种方式会触发类加载。这也是 Spring 读取 XML 配置时用的方式——class 属性的值直接传进来就能用。
方式二:类名.class
Class<?> clazz = UserDaoImpl.class;
对应类对象阶段。 编译期就确定了类,直接通过类的字面量获取。常用于已知具体类型的场景,比如获取 String.class 传给反射方法。
方式三:对象.getClass()
UserDaoImpl userDao = new UserDaoImpl();
Class<?> clazz = userDao.getClass();
对应运行时阶段。 已经有了实例对象,从对象身上反过来拿到它的 Class。常用于不确定对象具体类型时(比如方法参数是 Object 类型)。
三种方式对比
| 方式 | 语法 | 适用场景 |
|---|---|---|
Class.forName() |
传入字符串类名 | 类名运行时才知道(框架、配置驱动) |
类名.class |
编译期已知类型 | 传参、泛型等明确知道类型的场景 |
对象.getClass() |
已有实例 | 判断对象运行时的真实类型 |
五、Class 对象的唯一性
不管用哪种方式获取,同一个类在同一个类加载器下,Class 对象永远只有一个。
Class<?> c1 = Class.forName("com.xq.dao.impl.UserDaoImpl");
Class<?> c2 = UserDaoImpl.class;
UserDaoImpl obj = new UserDaoImpl();
Class<?> c3 = obj.getClass();
System.out.println(c1 == c2); // true
System.out.println(c2 == c3); // true
三个 == 比较的是对象地址,全部为 true。
这是因为类加载器在加载一个类时,会先检查方法区里是否已经存在对应的 Class 对象,如果已经存在就直接复用,不会重复创建。
六、回头看 Spring 的工作流程
现在可以完整描述 Spring XML 配置的底层过程了:
// Spring 源码里,大致是这样处理 XML 里的 bean 配置的:
// 第一步:从 XML 读取 class 属性的值(一个字符串)
String className = "com.xq.dao.impl.UserDaoImpl";
// 第二步:通过反射拿到 Class 对象
Class<?> clazz = Class.forName(className);
// 第三步:通过 Class 对象获取构造器,创建实例
Object instance = clazz.getDeclaredConstructor().newInstance();
// 这个 instance 就是 Spring 帮你创建好、放进容器里的 Bean
现在再看 XML 里的那行配置,是不是完全不一样了?
<bean id="userDao" class="com.xq.dao.impl.UserDaoImpl"></bean>
class 属性里的值,就是传给 Class.forName() 的那个字符串。
七、小结
| 问题 | 答案 |
|---|---|
| 反射是什么? | 运行时动态获取类信息并操作的能力 |
| 为什么需要反射? | 编译期无法确定类名时(如框架读配置),只能靠反射 |
| 反射的起点是什么? | Class 对象 |
怎么获取 Class 对象? |
forName()、.class、getClass() 三种方式 |
同一个类的 Class 对象有几个? |
永远只有一个(同一类加载器下) |
下一篇:拿到
Class对象之后,怎么获取字段、调用方法、创建对象。
更多推荐

所有评论(0)