没有理想的人不伤心

Java - 反射机制

2025/11/04
5
0

1 什么是反射

反射是 Java 提供的动态操作类 / 对象的能力 —— 不用在代码里写死 new 类名()对象.属性/方法,而是在程序运行时,通过「类的字节码(Class 对象)」,动态获取类的信息(属性、方法、构造器),并操作对象的属性和方法(哪怕是 private 修饰的)。

直白的讲,通过反射就是先获得 class 对象的内部结构信息,再操作,相当于开挂了。
image.png

image.png

2 获取 Class 对象的方式

反射的起点:获取 Class 对象

什么是 Class 对象:类的字节码示例,每个类只有一个 Class 对象

如何获取:共三种方法

2.1 类名.class

  • 方式1: 类名.class 最常用,编译期安全
Class<UserServiceImpl> clazz = UserServiceImpl.class;

2.2 对象.getClass()

  • 方式2:对象.getClass() 适用于已经有对象存在
UserServiceImpl userService = new UserServiceImpl();
Class<?> clazz = userService.getClass();

2.3 Class.forName("全类名")

  • 方式3:Class.forName("全类名") 动态加载,Spring扫描组件时使用
Class<?> clazz = Class.forName("com.example.demo.service.impl.UserServiceImpl");

3 反射四大核心操作

4大核心反射操作:结合spring底层来讲解

3.1 动态创建对象

如:spring 中实例化 bean
有两种常用方式通过反射来创建对象

// 下面已经默认获取了 Class 对象
// 方式1:调用无参构造器(Spring 实例化 Bean 默认用此方式)
UserServiceImpl bean1 = clazz.newInstance();// 已过时,jdk9+推荐方式2

//方式2:通过构造器对象创建(支持有参构造)
Constructor<UserServiceImpl> constructor = clazz.getDeclaredConstructor(); // 获得无参构造器
UserServiceImpl bean2 = constructor.newInstance();

//若有参构造如下:如 UserServiceImpl(String name)
Constructor<UserServiceImpl> constructor2 = clazz.getDeclaredConstructor(String.class); //参数由有参构造方法决定
UserServiceImpl bean3 = constructor2.newInstance("测试");
  • 关联 Spring:Spring 实例化 Bean 时,就是通过这种方式 —— 解析 @Service 后,获取 Class 对象,调用无参构造器创建 Bean 实例。

3.2 动态操作属性

如:Spring 中 @Autowired 注入
反射能获取类的所有属性,包括 private,并修改值,这是 Spring 注入依赖的核心

//默认已取得 Class 对象
// 1. 获取 private 属性,如 userMapper 是 private 修饰的
Field userMapperField = clazz.getDeclaredField("userMapper");

// 2. 关键:打破 private 访问限制,Spring 注入时必定会做这一步
userMapperField.setAccessible(true); // 暴力访问,忽略访问修饰符

// 3. 给对象的属性赋值, 模拟 Spring 注入 UserMapper
UserServiceImpl bean = clazz.newInstance(); //先创建对象
UserMapper mockUserMapper = new MockUserMapper(); // 模拟容器中的 UserMapper Bean
userMapperField.set(bean, mockUserMapper); // 给 bean 的 userMapper 字段赋值

// 4. 验证:获取属性值
UserMapper result = (UserMapper)userMapperField.get(bean);
System.out.println(result); // 输出mockUserMapper 说明注入成功

这里的mockUserMapper是什么东西??

  • @Autowired 底层就是这 3 步!Spring 找到依赖的 Bean 后,通过反射获取目标字段,打破访问限制,再用 field.set() 赋值,完成依赖注入(对应 Bean 生命周期第 2 步 “属性注入”)。

3.3 动态调用方法

如: Spring @PostConstruct 初始化
反射能调用类的所有方法,包括 private

// 1. 获取 @PostConstruct 标注的 init() 方法 (无参,void 返回值)
Method initMethod = clazz.getDeclaredMethod("init");

// 2. 打破访问限制,若init()是private,也可以调用
initMethod.setAccessible(true);

// 3. 调用方法(参数1:要调用的对象,参数2:方法入参,无参则传空)
UserServiceImpl bean = clazz.newInstance();
initMethod.invoke(bean); //执行 init(),控制台打印初始化日志

// 若方法有参,如 setName(String name)
Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);
setNameMethod.invoke(bean, "张三"); // 调用 setName("张三")
  • Spring 扫描到 @PostConstruct 后,通过反射找到该方法,调用 invoke() 执行,完成 Bean 初始化(对应 Bean 生命周期第 4 步 “初始化”)。

3.4 动态获取类信息

如:Spring 解析 BeanDefinition
反射能获取类的完整信息,包括类型、父类、接口、注解等

// 1. 获取类名
String className = clazz.getName(); // 全类名,如 com.example.springdemo.service.impl.UserServiceImpl
String simpleName = clazz.getSimpleName();// 简类名:UserServiceImpl

// 2. 获取类上的注解, 如 @Service
Service serviceAnnotation = clazz.getAnnotation(Service.class);
if(serviceAnnotation != null){
	System.out.println("类上由 @Service 注解"); // Spring 就是这么判断是否为 bean 的
}

// 3. 获取所有属性,包括private
Field[] allFields = clazz.getDeclaredFields();
for(Field fiedl : allFields){
	System.out.println("属性名:" + field.getName()); //输出 userMapper
}

// 4. 获取所有方法
Method[] allMethods = clazz.getDeclareMethods();
for(Method method : allMethods) {
	System.out.println("方法名:" + method.getName()); //输出 init、 findAll 等
}
  • Spring 扫描组件时,会通过反射获取类的注解(@Service/@Controller)、属性(依赖哪些类),并把这些信息封装成 BeanDefinition(Bean 说明书),后续创建 Bean 全靠这份说明书。

4 反射的核心价值

为什么 Spring 框架离不开反射:

  1. 解耦:框架(Spring)不用知道你写的类名、属性名,就能动态操作(比如你换个 Service 类,只要加 @Service,Spring 照样能通过反射管理);
  2. 突破访问限制:能操作 private 的属性和方法(比如 @Autowired 注入 private 的 userMapper,不用写 setter);
  3. 动态性:运行时才确定操作的类和对象,这是 Spring 实现 “自动配置”“依赖注入” 的前提。

5 实战:用反射模拟 Spring 注入 + 初始化

下面来根据上面的反射知识,实现一个简单的 mini-Spring,模拟注入依赖和调用初始化方法