作为一名 Java 开发工程师 ,你可能已经听说过"反射"(Reflection)这个词,也一定在使用 Spring、Hibernate、MyBatis 等框架时感受到它的强大。反射是 Java 语言中最强大、最灵活的特性之一,它允许我们在运行时动态地获取类的信息、调用方法、访问属性,甚至创建对象。
本文将带你全面掌握:
- 什么是反射?
- 反射的核心类(Class、Method、Field、Constructor)
- 如何通过反射动态操作类与对象
- 使用反射实现通用工具类、工厂模式、注解解析等
- 反射的优缺点与性能优化
- 反射在主流框架中的应用(Spring、MyBatis 等)
并通过丰富的代码示例和真实项目场景讲解,帮助你写出更灵活、更通用、更高级的 Java 代码。
🧱 一、什么是反射(Reflection)?
✅ 反射定义:
反射是 Java 提供的一种机制,它允许程序在**运行时(Runtime)**动态地获取类的信息(如类名、方法、属性等),并可以操作类或对象的内部结构。
✅ 反射的作用:
作用 | 描述 |
---|---|
动态加载类 | 在运行时根据类名加载类 |
动态创建对象 | 不通过 new 创建对象 |
动态调用方法 | 不通过对象直接调用方法 |
动态访问字段 | 不通过对象访问私有字段 |
获取类结构信息 | 方法、属性、构造器、注解等 |
实现通用框架 | Spring IOC、MyBatis ORM 等 |
🧠 二、反射的核心类与接口
反射的核心类都位于 java.lang.reflect
包中:
类/接口 | 说明 |
---|---|
Class |
表示类的类型,是反射的入口 |
Object |
所有类的父类,用于接收反射创建的对象 |
Method |
表示类中的方法 |
Field |
表示类中的字段 |
Constructor |
表示类的构造方法 |
Modifier |
获取方法/字段的修饰符 |
Annotation |
获取类、方法、字段上的注解信息 |
🧪 三、反射的基本使用
✅ 1. 获取 Class 对象的三种方式
ini
// 方式一:通过类名.class 获取
Class<?> clazz1 = String.class;
// 方式二:通过对象.getClass() 获取
String str = "hello";
Class<?> clazz2 = str.getClass();
// 方式三:通过 Class.forName("全限定类名") 获取
Class<?> clazz3 = Class.forName("java.util.ArrayList");
✅ 2. 动态创建对象(newInstance)
ini
// 获取 Class 对象
Class<?> clazz = Class.forName("com.example.User");
// 创建对象(调用无参构造方法)
Object obj = clazz.newInstance();
// 或者使用 Constructor 创建(可调用有参构造)
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object user = constructor.newInstance("Tom", 25);
✅ 3. 动态调用方法(invoke)
arduino
// 获取方法
Method method = clazz.getMethod("sayHello", String.class);
// 调用方法
method.invoke(obj, "反射你好!");
✅ 4. 动态访问字段(get/set)
csharp
// 获取字段
Field field = clazz.getDeclaredField("name");
// 设置可访问(突破 private 限制)
field.setAccessible(true);
// 设置字段值
field.set(obj, "Jerry");
// 获取字段值
Object value = field.get(obj);
System.out.println(value); // 输出 Jerry
🧩 四、反射的高级用法
✅ 1. 获取类的全部方法和字段
ini
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
System.out.println("方法名:" + m.getName());
}
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
System.out.println("字段名:" + f.getName());
}
✅ 2. 使用反射实现通用工厂模式
typescript
public class BeanFactory {
public static <T> T createBean(String className) {
try {
Class<?> clazz = Class.forName(className);
return (T) clazz.newInstance();
} catch (Exception e) {
throw new RuntimeException("创建对象失败", e);
}
}
}
✅ 3. 反射 + 注解:实现自定义注解处理器
less
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String value();
}
// 使用注解
public class MyClass {
@MyAnnotation("Hello")
public void myMethod() {
System.out.println("执行方法");
}
}
// 反射解析注解
public class AnnotationProcessor {
public static void process(Object obj) throws Exception {
for (Method method : obj.getClass().getDeclaredMethods()) {
if (method.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation anno = method.getAnnotation(MyAnnotation.class);
System.out.println("注解值:" + anno.value());
method.invoke(obj); // 执行带注解的方法
}
}
}
}
🧪 五、反射在主流框架中的应用
✅ 1. Spring IOC 容器(依赖注入)
Spring 通过反射自动创建 Bean、注入依赖、调用方法。
ini
// Spring 内部实现类似逻辑
Class<?> clazz = Class.forName("com.example.MyService");
Object service = clazz.newInstance();
field.setAccessible(true);
field.set(controller, service); // 注入依赖
✅ 2. MyBatis ORM 框架
MyBatis 利用反射将数据库结果集自动映射到 Java 对象。
ini
// 伪代码
Object user = clazz.newInstance();
Field idField = clazz.getDeclaredField("id");
idField.setAccessible(true);
idField.set(user, resultSet.getInt("id"));
✅ 3. JSON 序列化/反序列化(如 Jackson、Gson)
通过反射读取字段名和值,实现对象与 JSON 的互转。
scss
public static String toJson(Object obj) throws Exception {
Class<?> clazz = obj.getClass();
StringBuilder sb = new StringBuilder("{");
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
sb.append(""").append(field.getName()).append("":"").append(field.get(obj)).append("",");
}
sb.deleteCharAt(sb.length() - 1).append("}");
return sb.toString();
}
⚠️ 六、反射的优缺点与性能优化
✅ 优点:
优点 | 描述 |
---|---|
高度灵活 | 可动态加载类、调用方法、访问字段 |
支持插件化 | 如 Java SPI、Spring IOC |
通用性强 | 可实现通用工具类、ORM 映射、注解处理 |
支持热更新 | 可动态加载新类,实现热部署 |
❌ 缺点:
缺点 | 描述 |
---|---|
性能较低 | 反射调用比直接调用慢很多 |
破坏封装性 | 可以访问 private 成员 |
代码可读性差 | 反射代码不易阅读、调试困难 |
安全性风险 | 可能被恶意代码利用 |
✅ 性能优化建议:
优化方式 | 描述 |
---|---|
缓存 Class、Method、Field 对象 | 避免重复获取 |
使用 MethodHandle 或 VarHandle (JDK 7+)替代反射 |
提高性能 |
尽量避免在高频调用中使用反射 | 如在循环、热点代码中 |
使用 AOP 或注解处理器替代部分反射逻辑 | 如 Lombok、MapStruct |
使用缓存机制 | 如缓存 Method 对象或调用结果 |
🧱 七、反射最佳实践
实践 | 描述 |
---|---|
封装反射工具类 | 如 ReflectUtil、BeanUtils |
使用泛型增强类型安全 | 避免强制类型转换 |
使用 try-catch 捕获异常 | 反射方法可能抛出异常 |
使用 setAccessible(true) 突破访问限制 |
访问 private 成员 |
结合注解使用 | 实现自定义注解解析逻辑 |
合理使用缓存 | 提高反射调用效率 |
避免在性能敏感代码中使用反射 | 如高频循环、实时系统 |
配合 AOP、动态代理使用 | 如 JDK 动态代理、CGLIB |
🚫 八、常见误区与注意事项
误区 | 正确做法 |
---|---|
直接使用反射而不封装 | 应封装为工具类 |
忽略异常处理 | 必须捕获 IllegalAccessException 、InvocationTargetException 等 |
不使用缓存频繁获取 Class 对象 | 应缓存 Class、Method、Field |
不处理访问权限问题 | 应使用 setAccessible(true) |
在高频代码中使用反射 | 应避免或优化 |
不考虑泛型安全 | 应使用泛型返回类型 |
不考虑性能问题 | 应合理评估使用场景 |
不结合注解使用 | 应结合注解实现通用逻辑 |
📊 九、总结:Java 反射核心知识点一览表
内容 | 说明 |
---|---|
反射定义 | 运行时动态操作类和对象 |
核心类 | Class 、Method 、Field 、Constructor |
获取 Class 对象 | .class 、.getClass() 、Class.forName() |
创建对象 | newInstance() 、Constructor.newInstance() |
调用方法 | Method.invoke() |
访问字段 | Field.get() 、Field.set() |
注解解析 | isAnnotationPresent() 、getAnnotation() |
应用场景 | 工厂模式、IOC、ORM、JSON 序列化 |
性能问题 | 高频调用应缓存、避免直接反射 |
最佳实践 | 封装工具类、结合注解、缓存反射对象 |
📎 十、附录:Java 反射常用技巧速查表
技巧 | 示例 |
---|---|
获取类名 | clazz.getName() |
获取所有方法 | clazz.getDeclaredMethods() |
获取所有字段 | clazz.getDeclaredFields() |
获取构造方法 | clazz.getConstructor(Class<?>...) |
创建对象 | clazz.newInstance() |
调用方法 | method.invoke(obj, args) |
访问字段 | field.set(obj, value) |
设置可访问 | field.setAccessible(true) |
获取注解 | method.getAnnotation(MyAnnotation.class) |
判断是否有注解 | method.isAnnotationPresent(MyAnnotation.class) |
欢迎点赞、收藏、转发,也欢迎留言交流你在实际项目中遇到的反射相关问题。我们下期再见 👋
📌 关注我,获取更多Java核心技术深度解析!