反射是 Java 提供的动态操作类 / 对象的能力 —— 不用在代码里写死 new 类名()、对象.属性/方法,而是在程序运行时,通过「类的字节码(Class 对象)」,动态获取类的信息(属性、方法、构造器),并操作对象的属性和方法(哪怕是 private 修饰的)。
直白的讲,通过反射就是先获得 class 对象的内部结构信息,再操作,相当于开挂了。


反射的起点:获取 Class 对象
什么是 Class 对象:类的字节码示例,每个类只有一个 Class 对象
如何获取:共三种方法
类名.class 最常用,编译期安全Class<UserServiceImpl> clazz = UserServiceImpl.class;
对象.getClass() 适用于已经有对象存在UserServiceImpl userService = new UserServiceImpl();
Class<?> clazz = userService.getClass();
Class.forName("全类名") 动态加载,Spring扫描组件时使用Class<?> clazz = Class.forName("com.example.demo.service.impl.UserServiceImpl");
4大核心反射操作:结合spring底层来讲解
如: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("测试");
@Service 后,获取 Class 对象,调用无参构造器创建 Bean 实例。如: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 步 “属性注入”)。如: 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("张三")
@PostConstruct 后,通过反射找到该方法,调用 invoke() 执行,完成 Bean 初始化(对应 Bean 生命周期第 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 等
}
BeanDefinition(Bean 说明书),后续创建 Bean 全靠这份说明书。为什么 Spring 框架离不开反射:
@Service,Spring 照样能通过反射管理);@Autowired 注入 private 的 userMapper,不用写 setter);下面来根据上面的反射知识,实现一个简单的 mini-Spring,模拟注入依赖和调用初始化方法