Skip to content
第 33 / 250 章后端⏱ 10 分钟阅读

第 33 章:反射基础

学习目标

  • 理解反射是什么、为什么框架都靠它
  • 掌握 Class / Field / Method / Constructor 四大 API
  • 了解反射的性能代价

一、什么是反射?

正常写代码:编译期就知道类名、方法名。

java
User user = new User();
user.setName("张三");

反射:运行期才知道类名、方法名(从配置文件、注解里读出来)。

java
Class<?> clazz = Class.forName("com.taskflow.User");   // ① 字符串 → Class
Object user = clazz.getDeclaredConstructor().newInstance();
Method m = clazz.getMethod("setName", String.class);
m.invoke(user, "张三");

为什么框架都需要反射? Spring 不可能知道你会写什么类。它扫描到 @Service 注解后,只有一个类名字符串,必须靠反射把对象造出来。

二、获取 Class 对象的三种方式

java
// ① 类名.class —— 编译期确定,最快
Class<User> c1 = User.class;

// ② 对象.getClass() —— 有实例时用
Class<?> c2 = user.getClass();

// ③ Class.forName("全限定名") —— 运行期动态,框架最常用
Class<?> c3 = Class.forName("com.taskflow.User");

System.out.println(c1 == c2 && c2 == c3);  // true,同一个 Class 对象

为什么是同一个? 每个类在 JVM 中只有一份 Class 对象,由类加载器保证(同一个 ClassLoader 下)。

三、创建对象

java
Class<?> clazz = Class.forName("com.taskflow.User");

// ✅ JDK 9+ 推荐写法
Object obj = clazz.getDeclaredConstructor().newInstance();

// 带参构造
Constructor<?> con = clazz.getDeclaredConstructor(String.class, int.class);
Object user = con.newInstance("张三", 18);

// ❌ clazz.newInstance() 已废弃(JDK 9+):会吞掉构造器抛出的受检异常

四、操作字段

java
Field field = clazz.getDeclaredField("name");   // ① getDeclaredField 能拿到 private
field.setAccessible(true);                      // ② 关闭访问检查,才能操作 private

field.set(user, "李四");                         // ③ 赋值
String name = (String) field.get(user);         // ④ 取值
方法范围
getField(name)只能拿 public(含父类继承的)
getDeclaredField(name)本类所有(含 private),不含父类
getFields()所有 public
getDeclaredFields()本类所有

setAccessible(true) 就是 Spring 能给你的 private 字段注入 @Autowired 的原理。

五、调用方法

java
// ① 获取方法:方法名 + 参数类型列表
Method method = clazz.getDeclaredMethod("setName", String.class);
method.setAccessible(true);

// ② 调用:invoke(实例, 参数...)
method.invoke(user, "王五");

// ③ 静态方法:实例传 null
Method staticM = clazz.getDeclaredMethod("create");
staticM.invoke(null);

// ④ 有返回值
Object result = clazz.getMethod("getName").invoke(user);

六、读取注解

java
@Retention(RetentionPolicy.RUNTIME)   // ① 必须是 RUNTIME,反射才读得到
@Target(ElementType.TYPE)
public @interface Table {
    String value();
}

@Table("t_user")
public class User { }

// 读取
Table table = User.class.getAnnotation(Table.class);
System.out.println(table.value());   // t_user

这就是 MyBatis-Plus 的 @TableName("t_user") 能映射表名的原理。

七、完整实战:迷你 IOC 容器

java
public class MiniContainer {

    private final Map<Class<?>, Object> beans = new HashMap<>();

    /** 注册一个 bean:反射创建实例 */
    public void register(Class<?> clazz) throws Exception {
        Object instance = clazz.getDeclaredConstructor().newInstance();  // ①
        beans.put(clazz, instance);
    }

    /** 依赖注入:扫描 @Inject 字段并赋值 */
    public void inject() throws Exception {
        for (Object bean : beans.values()) {
            for (Field field : bean.getClass().getDeclaredFields()) {    // ②
                if (field.isAnnotationPresent(Inject.class)) {           // ③ 有注解才注入
                    field.setAccessible(true);
                    Object dep = beans.get(field.getType());             // ④ 按类型找依赖
                    field.set(bean, dep);                                // ⑤ 注入
                }
            }
        }
    }

    public <T> T get(Class<T> clazz) {
        return clazz.cast(beans.get(clazz));
    }
}

这 20 行就是 Spring IOC 的核心思想:扫描 → 反射创建 → 反射注入。剩下的是生命周期、循环依赖、AOP 代理等工程化细节。

八、反射的代价

问题说明
无法内联优化,比直接调用慢 10~100 倍
破坏封装setAccessible(true) 能改 private,绕过设计约束
编译期无检查方法名写错,运行时才炸
JDK 17 模块限制反射访问 JDK 内部类需要 --add-opens

优化手段

java
// ✅ 缓存 Method 对象,避免重复查找(getMethod 本身很慢)
private static final Map<String, Method> CACHE = new ConcurrentHashMap<>();

Method m = CACHE.computeIfAbsent("setName",
        k -> clazz.getDeclaredMethod(k, String.class));

规约:业务代码不要用反射;框架代码用反射时必须缓存 Method / Field。

九、本章小结

要点关键
反射运行期动态操作类
Class 获取.class / getClass() / forName()
创建对象getDeclaredConstructor().newInstance()
private 访问setAccessible(true)
注解读取注解必须 @Retention(RUNTIME)
性能慢,框架里必须缓存

动手练习

练习 1:基础题

写一个 toMap(Object obj) 方法,用反射把任意对象的所有字段转成 Map<String, Object>

练习 2:进阶题

实现一个简易 ORM:定义 @Table / @Column 注解,用反射生成 INSERT INTO t_user (name, age) VALUES (?, ?) SQL 语句。


下一章第 34 章:动态代理

本站基于 VitePress 构建 · 由 Codebook 团队维护