java反射全解
Java 反射全面详解(原理 + 核心API + 框架实战 + 面试考点)
从底层原理到 Spring/MyBatis 框架源码,一文彻底打通 Java 反射任督二脉
文章导读
反射(Reflection)是 Java 成为“动态语言”的关键,也是所有主流 Java Web 框架(Spring、MyBatis 等)的底层基石。本文分为基础原理篇与框架实战篇,从核心 API 用法到框架底层源码拆解,全程结合可运行代码与架构图,助你彻底吃透反射机制!
核心知识图谱
- 基础篇:反射本质、Class 对象原理、三大获取方式
- 实战篇:Constructor / Field / Method 核心 API 与避坑指南
- 框架篇:Spring IOC/DI/AOP、MyBatis 结果集映射底层反射拆解
- 进阶篇:框架如何解决反射性能问题、高频面试题标准答案
提示:CSDN 读者可直接点击右侧悬浮的 “目录” 按钮进行快速跳转阅读。
上篇:反射基础与核心 API 实战
一、反射基础概念
1. 什么是反射?
反射(Reflection) 是 Java 提供的一种运行时机制:程序在运行阶段,可以动态获取任意类的完整结构信息(类名、构造器、属性、方法、注解等),并能动态创建对象、读写属性、调用方法。
- 正常编码(静态绑定):编译期就确定要调用哪个类、哪个方法。
- 反射机制(动态绑定):编译期不知道操作的类,运行期才解析类元数据并执行操作。
2. 核心价值
- 突破面向对象的封装特性,动态操作私有成员;
- 主流框架的底层基石:Spring、MyBatis、SpringBoot、动态代理、RPC 框架、注解解析等全部依赖反射实现。
二、反射底层原理(面试重点)
1. Class 对象:反射的核心载体
Java 文件编译为 .class 字节码文件,当类被加载到 JVM 时:
- JVM 解析
.class文件中的元数据; - 在元空间(JDK8+) 中为该类创建唯一的
Class实例; - 这个
Class对象就是类在内存中的“模板”,一个类全局只会生成一个 Class 对象。
2. 为什么反射性能比直接调用慢?
- 动态解析:运行时解析元数据,而非编译期预处理;
- 安全校验:每次调用都会做权限校验、参数类型校验、装箱/拆箱;
- JIT 优化受限:JIT 编译器无法对反射代码做深度的内联优化。
结论:业务代码不建议频繁使用反射,框架层面为了“动态性”才被迫使用(框架内部会做缓存优化)。
三、第一步:获取 Class 对象(反射入口)
想要使用反射,必须先拿到目标类的 Class 对象。Java 提供 3 种主流获取方式:
| 获取方式 | 触发类初始化 | 阶段 | 灵活性 | 常用场景 |
|---|---|---|---|---|
类名.class |
否 | 编译期 | 低 | 已知类名,单纯获取 Class 对象 |
对象.getClass() |
是 | 运行期 | 低 | 已有对象实例,反向获取它的类型 |
Class.forName(全类名) |
默认是 | 运行期 | 极高 | 框架、配置文件读取类名、JDBC 驱动加载 |
// 方式1:类名.class
Class<Person> clazz1 = Person.class;
// 方式2:对象.getClass()
Person person = new Person();
Class<? extends Person> clazz2 = person.getClass();
// 方式3:Class.forName (最常用)
Class<?> clazz3 = Class.forName("com.example.Person");
四、反射核心 API 总览与实战
java.lang.reflect 包下 4 个核心类,对应类的四大组成部分:
前置准备:以下实战均基于如下
Person实体类(包含 public/private 属性、构造器与方法)。public class Person { public String name; private int age; public static String address = "默认地址"; public Person() {} private Person(String name) { this.name = name; } public void sayHello() { System.out.println("Hello: " + name); } private void secretMethod() { System.out.println("私有方法"); } public static void showAddress() { System.out.println("地址: " + address); } }
实战 1:反射构造器(Constructor)→ 动态创建对象
注意:JDK9 开始
Class#newInstance()被废弃,推荐统一使用Constructor#newInstance()。
// 1. 获取公共无参构造器
Constructor<Person> noArgCon = clazz.getConstructor();
Person p1 = noArgCon.newInstance();
// 2. 获取私有构造器(突破封装)
Constructor<Person> privateCon = clazz.getDeclaredConstructor(String.class);
privateCon.setAccessible(true); // 🔑 关键:关闭权限检查
Person p2 = privateCon.newInstance("张三");
实战 2:反射字段(Field)→ 读写成员属性
// 1. 操作 public 字段
Field nameField = clazz.getField("name");
nameField.set(person, "李四"); // 修改值
// 2. 操作 private 私有字段
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // 🔑 突破 private 权限
int age = (int) ageField.get(person); // 读取值
// 3. 操作 static 静态字段(实例参数传 null)
Field addressField = clazz.getDeclaredField("address");
addressField.set(null, "北京市");
实战 3:反射方法(Method)→ 调用成员方法
// 1. 调用 public 普通方法
Method sayHello = clazz.getMethod("sayHello");
sayHello.invoke(person); // 传入对象实例
// 2. 调用 private 私有方法
Method secret = clazz.getDeclaredMethod("secretMethod");
secret.setAccessible(true);
secret.invoke(person);
// 3. 调用 static 静态方法(实例参数传 null)
Method staticMethod = clazz.getDeclaredMethod("showAddress");
staticMethod.invoke(null);
高频易错点:getXXX vs getDeclaredXXX
这是反射最容易踩坑的地方,请牢记以下口诀:
- 带
Declared→ 只管当前类,权限全开放(含 private),不找父类; - 不带
Declared→ 只认 public,会向上找父类。
| 方法组 | 访问范围 | 权限限制 | 继承性 |
|---|---|---|---|
getField / getMethod / getConstructor |
仅 public 成员 | 只能拿公共成员 | 包含父类公共成员 |
getDeclaredXXX 系列 |
本类所有权限成员 | 本类全部成员 | 不包含父类任何成员 |
下篇:反射在主流 Web 框架中的深度应用
一、Web 框架为什么重度依赖反射?
现代 Web 框架的核心设计思想是 配置驱动 / 注解驱动 / 动态解耦。
框架启动时并不知道要实例化哪些类、调用哪个方法,必须在运行时动态解析类结构、创建对象、调用方法。反射就是实现「运行时动态操作」的唯一基础。
二、主流框架反射应用详解
1. Spring Framework(反射使用最多)
场景 A:IOC 容器 —— 动态创建 Bean 对象
我们只需加 @Service 注解,Spring 就能自动创建对象。底层完全依赖反射:
场景 B:DI 依赖注入 —— 反射给私有属性赋值
@Autowired 直接标在私有字段上,底层如何赋值?
// 模拟 Spring 字段自动注入核心逻辑
Field field = serviceClazz.getDeclaredField("userDao");
field.setAccessible(true); // Spring 核心操作:突破 private 权限
field.set(serviceBean, daoBean); // 反射赋值 = 依赖注入
场景 C:Spring MVC —— 请求参数自动绑定
前端传递 name=张三&age=18,SpringMVC 自动封装到 User 对象。
底层逻辑:反射创建 User 对象 → 遍历参数 → 反射 Field 给 name/age 私有字段赋值。
2. MyBatis(ORM 框架代表)
场景 A:Mapper 接口代理(无实现类也能执行 SQL)
只写 Mapper 接口,不写实现类。底层通过 JDK 动态代理生成代理类,调用方法时反射获取方法名、注解,匹配对应的 SQL 语句执行。
场景 B:结果集映射(ResultSet → Java 实体)【最高频】
数据库查询返回 ResultSet,MyBatis 自动映射到 Java 实体,这是反射 Field 最经典的应用。
3. JSON 序列化框架(Jackson / FastJSON)
- 对象 → JSON:反射遍历所有
Field,调用field.get()拼接字符串。 - JSON → 对象:反射调用构造器创建实例,反射
Field.set()给私有属性赋值。
4. Dubbo(RPC 框架)
远程调用的核心:消费者代理类反射获取方法名和参数封装网络请求;提供者接收请求后,反射找到实现类并调用目标方法。
三、框架如何解决「反射性能差」的问题?
反射运行慢、有权限校验开销。工业级框架做了多层优化(面试高频考点):
- 缓存反射元数据(最核心):启动阶段一次性解析所有
Class/Field/Method存入 Map 缓存,运行时直接读缓存。 - 复用权限开关:对私有成员统一执行一次
setAccessible(true)并缓存,避免重复权限检查。 - 字节码技术替代:Spring、MyBatis 底层集成 ASM、Javassist,在热点路径直接生成字节码,绕过反射。
- 编译期预处理(APT):如 Lombok、MyBatis-Plus,在编译期生成辅助代码,运行时无需反射。
四、高频面试题汇总(附标准答案)
Q1:什么是反射?反射的原理是什么?
答:运行期动态获取类信息、操作类成员。原理是基于类加载后 JVM 元空间中唯一的
Class对象,通过它获取Constructor/Field/Method等元数据进行操作。
Q2:getField 和 getDeclaredField 有什么区别?
答:
getField仅获取 public 字段(包含父类);getDeclaredField获取本类所有权限字段(包含 private,但不含父类)。
Q3:Spring IOC 创建 Bean 为什么要用反射?能不能直接 new?
答:不能直接 new。因为框架启动前不知道要创建哪些类,类名来自注解或 XML 配置(字符串形式),编译期无法硬编码,只能运行时通过
Class.forName()+ 反射构造器创建对象。
Q4:Spring 中 @Autowired 直接注入私有字段,底层怎么实现?
答:使用
getDeclaredField()获取私有字段,调用setAccessible(true)关闭 Java 语法层的权限检查,再通过Field.set()反射赋值。
Q5:MyBatis 为什么不需要 Mapper 实现类?结果集映射底层原理是什么?
答:
- Mapper 接口由 JDK 动态代理生成代理对象,调用时反射解析方法元数据匹配 SQL;
- 结果集映射通过反射创建实体对象,遍历列名匹配 Field,突破私有权限后反射赋值。
Q6:为什么 Spring 启动慢、运行快?
答:启动阶段集中做反射解析、类扫描和元数据缓存(耗时);运行阶段直接使用内存缓存,规避了重复反射的开销(极快)。
五、总结
- 反射是现代 Java Web 框架的「动态基石」:所有注解驱动、配置驱动、解耦设计,都离不开运行时反射。
- 框架反射用法与原生 API 完全对应:
- 创建对象 →
Constructor.newInstance() - 读写属性 →
Field.set() / get()(重点setAccessible(true)) - 调用方法 →
Method.invoke()
- 创建对象 →
- 日常业务代码不建议滥用反射,但理解框架源码必须吃透反射。
- 框架通过 「缓存 + 字节码 + 编译期预处理」 解决了反射性能短板,做到了动态性与性能的完美兼顾。
🌟 原创不易,如果这篇文章对你有帮助,欢迎 点赞 👍 | 收藏 ⭐ | 关注 ➕ 三连支持!
有任何问题或不同见解,欢迎在评论区留言交流探讨~
更多推荐

所有评论(0)