Java反射机制:Spring IOC背后的核心技术
开篇:为什么框架能自动创建对象?
你有没有想过:Spring里你只写了一个@Service注解,Spring就能自动创建对象、注入依赖、调用方法。它是怎么做到的?
答案就是反射。反射是Java的"自省"能力,让程序在运行时能查看和操作自己的结构。
本文不讲高深理论,只讲反射的核心用法和实际应用场景。
一、什么是反射
反射(Reflection)是指在运行状态中,程序可以获取任意一个类的内部信息,并且可以操作它的属性、方法、构造器。
正常情况下,我们写代码是"知道类名,直接new对象"。反射则是"只知道类名字符串,也能创建对象并调用方法"。
java
// 传统方式:知道类名
User user = new User();
// 反射方式:只知道类名字符串
Class<?> clazz = Class.forName("com.example.User");
Object user = clazz.newInstance();
【面试高频】 反射让Java从"静态语言"具备了一定的动态性。
二、获取Class对象的三种方式
Class对象是反射的入口。获取Class对象有三种方式:
java
// 方式一:类名.class
Class<User> clazz1 = User.class;
// 方式二:对象.getClass()
User user = new User();
Class<?> clazz2 = user.getClass();
// 方式三:Class.forName()
Class<?> clazz3 = Class.forName("com.example.User");
一个类在JVM中只有一个Class对象。无论用哪种方式获取,比较结果都是true。
java
System.out.println(clazz1 == clazz2); // true
System.out.println(clazz2 == clazz3); // true
三、反射操作构造器
3.1 获取构造器
java
Class<?> clazz = User.class;
// 获取所有public构造器
Constructor<?>[] constructors = clazz.getConstructors();
// 获取指定参数类型的构造器
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
3.2 创建对象
java
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object user = constructor.newInstance("张三", 20);
【面试高频】
Class.newInstance()在JDK9之后被标记为过时,推荐使用Constructor.newInstance()。
四、反射操作方法
4.1 获取方法
java
Class<?> clazz = User.class;
// 获取所有public方法(包括继承的)
Method[] methods = clazz.getMethods();
// 获取指定方法
Method setName = clazz.getMethod("setName", String.class);
4.2 调用方法
java
Object user = clazz.newInstance();
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "李四");
invoke的第一个参数是方法所属的对象,后面的参数是方法实参。
4.3 调用私有方法
java
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true); // 暴力破解访问权限
privateMethod.invoke(user);
五、反射操作字段
5.1 获取字段
java
Class<?> clazz = User.class;
// 获取所有public字段
Field[] fields = clazz.getFields();
// 获取指定字段
Field nameField = clazz.getDeclaredField("name");
5.2 修改字段值
java
Object user = clazz.newInstance();
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(user, "王五");
String name = (String) nameField.get(user);
System.out.println(name); // 王五
六、反射的典型应用场景
6.1 Spring IOC容器
Spring通过反射读取配置文件或注解,自动创建Bean对象:
java
// 配置文件
<bean id="userService" class="com.example.UserService"/>
// Spring底层
Class<?> clazz = Class.forName("com.example.UserService");
Object bean = clazz.newInstance();
6.2 MyBatis结果集映射
MyBatis查询数据库后,通过反射把数据库字段映射到Java对象属性:
java
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(obj, resultSet.getObject(columnName));
6.3 JDBC加载驱动
java
Class.forName("com.mysql.cj.jdbc.Driver");
这行代码就是利用反射把数据库驱动类加载到内存。
6.4 序列化与反序列化
JSON框架(如FastJSON、Jackson)通过反射读取对象字段,把对象转成JSON字符串,或者把JSON字符串转成对象。
七、手写简易IOC容器
下面用反射实现一个极简版IOC容器,让你理解Spring的核心原理。
7.1 定义注解
java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyComponent {
String value() default "";
}
7.2 标注Bean
java
@MyComponent
public class UserService {
public void sayHello() {
System.out.println("Hello from UserService");
}
}
7.3 IOC容器
java
public class MyIocContainer {
private Map<String, Object> beanMap = new HashMap<>();
public void scan(String packageName) throws Exception {
// 简单起见,直接加载指定类
Class<?> clazz = Class.forName(packageName);
if (clazz.isAnnotationPresent(MyComponent.class)) {
Object bean = clazz.newInstance();
String beanName = clazz.getSimpleName();
beanName = beanName.substring(0, 1).toLowerCase() + beanName.substring(1);
beanMap.put(beanName, bean);
}
}
public Object getBean(String name) {
return beanMap.get(name);
}
}
7.4 使用容器
java
public class IocDemo {
public static void main(String[] args) throws Exception {
MyIocContainer container = new MyIocContainer();
container.scan("com.example.UserService");
UserService userService = (UserService) container.getBean("userService");
userService.sayHello();
}
}
【面试高频】 Spring IOC的核心就是"通过反射创建对象并管理对象生命周期"。
八、反射的优缺点
8.1 优点
- 可以在运行时动态创建对象
- 可以操作私有成员
- 是框架实现的基础
8.2 缺点
- 性能比直接调用低
- 破坏了封装性
- 代码可读性下降
【面试陷阱】 反射性能差不是绝对的。JDK对反射做了很多优化,普通场景下差距不大。但在高并发、大量反射调用的场景要注意。
九、开发踩坑实录
坑 1:Class.forName类名写错
java
Class<?> clazz = Class.forName("com.example.user"); // 类名大小写错误
后果 :ClassNotFoundException。
正确做法:类名必须是完整限定名,且大小写一致。
坑 2:调用私有成员没setAccessible
java
Field field = clazz.getDeclaredField("name");
field.set(user, "张三"); // 报 IllegalAccessException
正确做法 :先调用field.setAccessible(true)。
坑 3:反射创建对象时无参构造器被删除
java
public class User {
public User(String name) {} // 只有有参构造器
}
User user = User.class.newInstance(); // 报错
正确做法:明确调用对应参数的构造器。
坑 4:invoke参数类型不匹配
java
Method method = clazz.getMethod("setAge", int.class);
method.invoke(user, "20"); // 参数类型不匹配
正确做法:传入与声明类型一致的实际参数。
坑 5:反射滥用导致代码难以维护
反射虽然强大,但过度使用会让代码变成"黑魔法"。普通业务代码中应优先使用正常面向对象写法。
十、面试速记卡
10.1 核心API速查
| API | 作用 |
|---|---|
| Class.forName() | 加载类并获取Class对象 |
| clazz.newInstance() | 创建对象(JDK9后过时) |
| getConstructor() | 获取构造器 |
| getMethod() | 获取方法 |
| getField() | 获取public字段 |
| getDeclaredMethod() | 获取任意方法(含私有) |
| getDeclaredField() | 获取任意字段(含私有) |
| setAccessible(true) | 暴力破解访问权限 |
| invoke() | 调用方法 |
10.2 高频面试题
- 什么是反射?有什么作用?
- 获取Class对象的三种方式是什么?
- 反射如何调用私有方法?
- Spring IOC底层是怎么实现的?
- 反射的优缺点是什么?
- 为什么反射性能比直接调用低?
- JDBC中
Class.forName()的作用是什么?
10.3 口诀总结
反射入口是Class,三种方式获取它
forName最常用,全限定名别写差
构造器和方法,都能反射拿
私有成员加setAccessible,才能操作它
框架底层爱用它,Spring IOC靠它发
反射虽好别滥用,代码维护会抓瞎
十一、小结
反射是Java最强大的特性之一,也是大多数框架的底层基础。
掌握反射的核心API、理解Spring IOC的实现原理、能手写一个简易容器,面试和工作都够用了。
记住:反射是一把双刃剑,能解决问题,但不要为了用而用。