一、什么是反射(Reflection)
反射机制:程序在运行期,动态获取类的信息、操作类 / 对象的属性、方法、构造器。
正常编码(编译期):
我们提前知道类名,直接new 对象(),调用方法,代码在编译阶段就确定要操作哪个类。
java
User user = new User();
user.setName("张三"); // 编译时就确定调用 User.setName
反射(运行期):
编译期不知道类是谁,运行时拿到类的 Class 对象,反向解析出这个类有什么构造方法、成员变量、成员方法,然后动态创建对象、读写字段、调用方法。
核心入口:
java.lang.Class,每一个类加载后,在 JVM 里都会生成唯一的Class对象,代表这个类型。
获取 Class 对象 3 种方式
java
// 方式1:类名.class (不会触发类的静态代码块执行)
Class<User> clazz1 = User.class;
// 方式2:对象.getClass() (必须已有实例)
User u = new User();
Class<? extends User> clazz2 = u.getClass();
// 方式3:Class.forName("全类名") 最常用,会加载类、执行静态代码块
Class<?> clazz3 = Class.forName("com.demo.User");
二、反射能做什么(核心 API)
都在java.lang.reflect包:
Constructor:构造器,用来 new 对象Field:成员变量,读取 / 修改属性Method:成员方法,调用方法Modifier:修饰符工具,判断 public/private/static 等
重点:
反射可以突破访问权限(setAccessible (true)),可以访问 private 方法 / 属性,这也是反射的双刃剑。
三、完整示例代码
先准备实体类 User
java
package com.demo;
public class User {
// 私有字段
private String name;
public Integer age;
// 无参构造
public User(){}
// 有参构造
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
// 私有方法
private String getSecret() {
return "我是私有秘密";
}
// public方法
public void sayHello(String msg) {
System.out.println("hello:" + msg);
}
// getter/setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
示例 1:反射获取构造器,创建对象
java
import java.lang.reflect.Constructor;
public class ReflectDemo {
public static void main(String[] args) throws Exception {
// 1. 获取Class对象
Class<?> clazz = Class.forName("com.demo.User");
// 获取无参构造器创建实例
Constructor<?> noArgCon = clazz.getDeclaredConstructor();
User user1 = (User) noArgCon.newInstance();
System.out.println(user1);
// 获取有参构造器
Constructor<?> argCon = clazz.getDeclaredConstructor(String.class, Integer.class);
User user2 = (User) argCon.newInstance("李四", 20);
System.out.println(user2.getName() + " , " + user2.age);
}
}
getConstructor()只能拿 public 构造;getDeclaredConstructor()拿到所有修饰符的构造,包括 private。
示例 2:反射读写成员变量(包含私有属性)
java
Class<?> clazz = Class.forName("com.demo.User");
User user = (User) clazz.getDeclaredConstructor().newInstance();
// 获取私有字段 name
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 关闭权限检查,允许访问private
nameField.set(user, "王五"); // 设置值
String nameVal = (String) nameField.get(user); // 获取值
System.out.println(nameVal);
示例 3:反射调用方法(public /private)
Class<?> clazz = Class.forName("com.demo.User");
User user = (User) clazz.getDeclaredConstructor().newInstance();
// 调用public方法 sayHello,参数类型String
Method sayHelloMethod = clazz.getDeclaredMethod("sayHello", String.class);
sayHelloMethod.invoke(user, "反射调用方法");
// 调用私有方法 getSecret,无参数
Method secretMethod = clazz.getDeclaredMethod("getSecret");
secretMethod.setAccessible(true);
String result = (String) secretMethod.invoke(user);
System.out.println(result);
method.invoke(对象实例, 参数列表)如果调用 static 方法,第一个参数传null。
四、反射优缺点
✅ 优点
- 高度灵活:运行期动态创建对象、调用方法,不用编译期硬编码类。框架的基石。
- 解耦:配置文件写类全名,程序读取配置,动态加载类。
❌ 缺点
- 性能低:绕过编译优化,需要做大量权限校验、类型解析;频繁反射会有开销。
- 破坏封装 :
setAccessible(true)可以访问私有变量 / 私有方法,违背面向对象封装。 - 编译期失去类型检查:类型错误只有运行时才抛出异常,不易排查。
- 安全问题:安全管理器下会被限制;很多安全扫描会标记反射。
五、真实业务场景示例(重点)
场景 1:框架底层(Spring IOC 容器)
Spring 读取 xml / 注解里的 bean 全类名,使用反射实例化对象、注入属性。
Spring 为什么要用反射?
程序员写的 Bean 类,Spring 在写框架代码的时候根本不知道类名,只能运行时动态创建实例。
伪代码模拟 Spring 创建 Bean:
java
String beanClassName = "com.demo.User"; // 从配置读取
Class<?> clazz = Class.forName(beanClassName);
Object bean = clazz.getDeclaredConstructor().newInstance();
// 反射setter注入属性
Method setName = clazz.getDeclaredMethod("setName", String.class);
setName.invoke(bean, "Spring注入名字");
场景 2:ORM 框架 MyBatis
数据库查询结果 ResultSet,反射实例化实体类,反射给实体字段赋值。
数据库表字段 -> 实体属性,MyBatis 不知道你写的 POJO,只能反射 setter 或者直接给 field 赋值。
场景 3:单元测试 JUnit
JUnit 扫描@Test注解:运行时反射找到所有标记 @Test 的方法,自动执行。
场景 4:通用工具:对象拷贝工具(BeanUtils)
比如 Spring BeanUtils、Hutool BeanUtil。
输入 source 对象,target 对象,反射遍历所有字段,把同名字段的值复制过去,不用手写一堆 get/set。 伪代码:
java
public static void copy(Object source, Object target) throws Exception {
Class<?> sourceCls = source.getClass();
Class<?> targetCls = target.getClass();
Field[] fields = sourceCls.getDeclaredFields();
for (Field f : fields) {
f.setAccessible(true);
Object val = f.get(source);
Field targetField = targetCls.getDeclaredField(f.getName());
targetField.setAccessible(true);
targetField.set(target, val);
}
}
场景 5:RPC 框架(Dubbo)
服务消费者,拿到接口,动态代理。底层使用反射调用远程接口方法。
场景 6:序列化工具(Jackson / FastJSON)
序列化对象转 JSON:
反射扫描对象所有字段,读取字段值转 json; 反序列化 JSON 转对象:反射创建实例,给字段赋值。
六、补充:反射 + 动态代理(延伸)
反射常和动态代理配合,AOP 底层就是 JDK 动态代理,依赖反射。
JDK 动态代理只能代理接口,运行期生成代理类,通过反射调用目标方法,在前后增加切面逻辑(日志、事务)。
七、扩展问题
getClass() 和 .class 和 Class.forName() 区别
特性 obj.getClass() 类名.class Class.forName ("全类名") 语法 实例方法,必须有对象 类字面量,无需实例 静态方法,传入字符串全类名 多态 ✅ 返回运行时真实类型 ❌ 固定就是这个类 ❌ 字符串指定哪个类就是哪个 类初始化 一定已初始化 不触发静态代码块 默认触发静态代码块(可关闭) 类型检查 运行期 编译期校验 运行期才校验,类不存在抛异常 使用场景 拿到对象真实类型、反射取实例信息 反射提前拿到 Class、工具类,不想执行静态块 根据字符串动态加载类(JDBC、SPI 框架)
getConstructor vs getDeclaredConstructor
getConstructor只拿 public 构造;
getDeclaredConstructor获取本类全部构造,不限访问权限。
setAccessible(true) 作用,是否可以永久修改访问权限?
只是关闭本次反射访问检查,不会修改源码里的访问修饰符。
反射性能差怎么优化?
使用
MethodHandle、缓存 Constructor/Method 对象,不要循环内反复获取 Class、Method。
反射能不能获取泛型真实类型?
可以通过
ParameterizedType获取,这就是 MyBatis 解析泛型 List<User>的原理。