Java 反射详解 + 场景示例

一、什么是反射(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包:

  1. Constructor:构造器,用来 new 对象
  2. Field:成员变量,读取 / 修改属性
  3. Method:成员方法,调用方法
  4. 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。

四、反射优缺点

✅ 优点

  1. 高度灵活:运行期动态创建对象、调用方法,不用编译期硬编码类。框架的基石。
  2. 解耦:配置文件写类全名,程序读取配置,动态加载类。

❌ 缺点

  1. 性能低:绕过编译优化,需要做大量权限校验、类型解析;频繁反射会有开销。
  2. 破坏封装 :setAccessible(true)可以访问私有变量 / 私有方法,违背面向对象封装。
  3. 编译期失去类型检查:类型错误只有运行时才抛出异常,不易排查。
  4. 安全问题:安全管理器下会被限制;很多安全扫描会标记反射。

五、真实业务场景示例(重点)

场景 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>的原理。

相关推荐
零基础1234 小时前
PDF 处理工具全攻略:pdf24、PDFgear、Adobe Acrobat DC 与 Stirling-PDF 深度对比
java·开发语言·pdf
SL_staff4 小时前
制造业数字化协同:为什么甘特图只是起点,不是终点
java·开源·全栈
anew___4 小时前
《从零手写操作系统 (29):管道与重定向进阶——命名管道、Here Document与Shell语法扩展》
java·开发语言·前端·javascript·网络
Mikko74 小时前
JVM 线上排查实战(六):JFR 怎么用?JDK 8 要不要加 UnlockCommercialFeatures、jfr 命令在哪、录的文件为什么读不了
java·运维·jvm·后端
奇牙coding4 小时前
GPT-5.5 API 报 401 但 GPT-5.4 正常怎么办?不是 Key 失效,是 Organization 头的强制校验变了
java·网络·gpt·ai
a努力。5 小时前
Context-State-Memory三重信息架构揭秘
java·服务器·前端
captain3765 小时前
Maven
java·后端·idea
liangshanbo12155 小时前
主系统登录后,子系统怎么实现自动登录?
java·网络·数据库
ShineWinsu6 小时前
对于Redis:缓存的解析
java·redis·缓存·缓存穿透·缓存击穿·缓存雪崩·缓存预热