Wrayの知识库 Wrayの知识库
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
  • Java章节编写规范
  • Java基础

    • Java概述
    • Java语法
    • 面向对象编程
    • Java数组
    • String字符串
    • 异常处理
    • 基本类型与包装类
    • Object类与常用方法
    • 泛型
    • 枚举与注解
    • 反射
      • 1. Class 对象
      • 2. 获取类信息
      • 3. 构造对象
      • 4. 操作字段
      • 5. 调用方法
      • 6. 读取注解
      • 7. 反射与泛型
      • 8. 反射与访问控制
      • 9. 反射性能
      • 10. 反射常见异常
      • 11. 反射在框架中的典型应用
        • 11.1 Spring Bean 创建
        • 11.2 JSON 反序列化
        • 11.3 单元测试
      • 12. 开发建议
      • Tips 快问快答
    • 值传递与引用
    • Lambda表达式与函数式接口
    • Stream API
    • Optional与空值处理
    • 日期时间API
    • Java现代语法特性
  • Java集合

  • Java并发

  • Java IO

  • JVM

目录

反射

反射是 Java 在运行期检查和操作类、字段、方法、构造器、注解等结构的能力。它让程序可以在“不提前写死具体类”的情况下,根据运行时信息创建对象、调用方法、读取注解。

框架大量依赖反射:

  • Spring 创建 Bean、注入依赖、读取注解。
  • MyBatis 把查询结果映射成对象。
  • Jackson 把 JSON 转成 Java 对象。
  • JUnit 扫描并执行测试方法。
  • 动态代理根据接口生成代理对象。

反射是理解 Java 框架的入口,但也要注意性能、封装破坏和模块访问限制。

# 1. Class 对象

每个被 JVM 加载的类,在运行时都有一个 Class 对象表示它的类型信息。

User.class 文件
   │ 类加载
   ▼
JVM 中的 Class<User> 对象
   │
   ├─ 类名
   ├─ 字段
   ├─ 方法
   ├─ 构造器
   ├─ 注解
   └─ 父类 / 接口

获取 Class 的常见方式:

Class<String> c1 = String.class;

String text = "hello";
Class<?> c2 = text.getClass();

Class<?> c3 = Class.forName("java.lang.String");

对比:

方式 说明
String.class 编译期已知类型
obj.getClass() 通过对象获取运行时类型
Class.forName 通过类全限定名加载

Class.forName 可能触发类加载和初始化,需要处理 ClassNotFoundException。

# 2. 获取类信息

Class<?> clazz = User.class;

System.out.println(clazz.getName());
System.out.println(clazz.getSimpleName());
System.out.println(clazz.getPackageName());
System.out.println(clazz.getSuperclass());
System.out.println(Arrays.toString(clazz.getInterfaces()));

常用 API:

API 作用
getName() 全限定类名
getSimpleName() 简单类名
getPackageName() 包名
getSuperclass() 父类
getInterfaces() 实现的接口
isAnnotationPresent() 是否存在某注解

# 3. 构造对象

推荐通过构造器对象创建实例:

Constructor<User> constructor = User.class.getConstructor(String.class);
User user = constructor.newInstance("Tom");

如果是无参构造器:

User user = User.class.getDeclaredConstructor().newInstance();

旧写法 clazz.newInstance() 已不推荐,因为它只能调用 public 无参构造器,异常表达也不够清晰。

创建流程:

Class&lt;User>
   │ getDeclaredConstructor
   ▼
Constructor&lt;User>
   │ newInstance
   ▼
User 对象

如果构造器是私有的:

Constructor<User> constructor = User.class.getDeclaredConstructor();
constructor.setAccessible(true);
User user = constructor.newInstance();

setAccessible(true) 会绕过 Java 语言访问检查,但在模块化环境或安全限制下可能失败。

# 4. 操作字段

示例类:

public class User {
    private Long id;
    private String name;
}

获取字段:

Field field = User.class.getDeclaredField("name");
field.setAccessible(true);

读取字段:

String name = (String) field.get(user);

设置字段:

field.set(user, "Tom");

字段 API:

API 作用
getFields() 获取 public 字段,包含父类
getDeclaredFields() 获取当前类声明字段,包含 private
getField(name) 获取指定 public 字段
getDeclaredField(name) 获取当前类指定字段

反射设置字段会绕过 setter,因此可能破坏对象校验逻辑。框架可以这样做,业务代码要谨慎。

# 5. 调用方法

Method method = User.class.getDeclaredMethod("setName", String.class);
method.setAccessible(true);
method.invoke(user, "Alice");

调用有返回值方法:

Method getter = User.class.getDeclaredMethod("getName");
String name = (String) getter.invoke(user);

方法 API:

API 作用
getMethods() 获取 public 方法,包含父类
getDeclaredMethods() 获取当前类声明方法,包含 private
getMethod(name, parameterTypes) 获取指定 public 方法
getDeclaredMethod(name, parameterTypes) 获取当前类指定方法

invoke 抛出的底层异常会被包装在 InvocationTargetException 中:

try {
    method.invoke(user);
} catch (InvocationTargetException e) {
    Throwable target = e.getTargetException();
    throw target;
}

排查反射调用异常时,要看 getTargetException()。

# 6. 读取注解

定义注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AuditLog {
    String value();
}

使用:

public class OrderService {
    @AuditLog("创建订单")
    public void createOrder() {
    }
}

读取:

Method method = OrderService.class.getDeclaredMethod("createOrder");
AuditLog auditLog = method.getAnnotation(AuditLog.class);

if (auditLog != null) {
    System.out.println(auditLog.value());
}

流程:

扫描 Class
  │
  ▼
获取 Method
  │
  ▼
读取 Runtime 注解
  │
  ▼
根据注解值执行框架逻辑

这就是很多 AOP、权限、日志、事务框架的基础套路。

# 7. 反射与泛型

泛型运行期会擦除,但字段、父类、方法签名中声明的泛型信息可以通过 Type 读取。

public class UserRepository extends BaseRepository<User> {
}

读取父类泛型:

Type type = UserRepository.class.getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) type;
Type actualType = parameterizedType.getActualTypeArguments()[0];

常见 Type 子类型:

类型 说明
Class 普通类
ParameterizedType 参数化类型,如 List<String>
TypeVariable 类型变量,如 T
WildcardType 通配符,如 ? extends Number
GenericArrayType 泛型数组

框架使用这些信息处理泛型 Repository、HTTP 返回类型、JSON 反序列化等。

# 8. 反射与访问控制

setAccessible(true) 可以关闭 Java 语言访问检查。

Field field = User.class.getDeclaredField("name");
field.setAccessible(true);

但这并不意味着任何环境都能随意访问:

  • 安全管理器或运行策略可能限制。
  • Java 模块系统可能禁止深反射访问未开放包。
  • 框架可能需要添加 --add-opens 参数。

开发中不要把反射当成访问 private 成员的常规方式。private 表达了封装边界,绕过它会增加维护风险。

# 9. 反射性能

反射比直接调用慢,原因包括:

  • 需要查找元数据。
  • 需要访问检查。
  • 参数和返回值可能需要装箱、拆箱。
  • 方法调用路径更复杂。

优化方式:

方法 说明
缓存 Class、Method、Field 避免重复查找
启动期扫描 将成本放到启动阶段
使用 MethodHandle 某些场景性能更好
生成字节码 高性能框架常用
避免业务热路径频繁反射 热点代码直接调用

缓存示例:

private static final Map<String, Method> METHOD_CACHE = new ConcurrentHashMap<>();

Spring、Jackson 等框架都会大量缓存反射元数据。

# 10. 反射常见异常

异常 常见原因
ClassNotFoundException 类名错误或 classpath 中不存在
NoSuchMethodException 方法名或参数类型不匹配
NoSuchFieldException 字段不存在
IllegalAccessException 没有访问权限
InstantiationException 抽象类、接口等不能实例化
InvocationTargetException 被调用方法内部抛出异常

反射问题排查顺序:

类是否加载到 classpath
  │
  ▼
类名、方法名、字段名是否正确
  │
  ▼
参数类型是否完全匹配
  │
  ▼
访问权限是否允许
  │
  ▼
底层方法是否自己抛了异常

# 11. 反射在框架中的典型应用

# 11.1 Spring Bean 创建

扫描类
  │
  ▼
发现 @Component
  │
  ▼
选择构造器
  │
  ▼
反射创建对象
  │
  ▼
注入字段或方法
  │
  ▼
放入容器

# 11.2 JSON 反序列化

JSON 字段 name
  │
  ▼
匹配 Java 字段 name 或 setter
  │
  ▼
类型转换
  │
  ▼
反射设置对象属性

# 11.3 单元测试

扫描测试类
  │
  ▼
查找 @Test 方法
  │
  ▼
反射调用测试方法
  │
  ▼
收集执行结果

# 12. 开发建议

  • 业务代码优先使用普通方法调用,不要过度反射。
  • 框架、工具、通用组件可以使用反射提升灵活性。
  • 反射元数据要缓存。
  • 反射异常要保留根因。
  • 不要用反射随意破坏封装。
  • 对模块化项目,要关注包是否开放给反射访问。

# Tips 快问快答

Q:反射是什么? A:运行期检查和操作类、方法、字段、构造器、注解等结构的能力。

Q:Class 对象是什么? A:JVM 加载类后,用 Class 对象表示该类的运行时类型信息。

Q:获取 Class 有哪些方式? A:类名.class、对象.getClass()、Class.forName("全限定类名")。

Q:反射能访问 private 字段吗? A:可以尝试通过 setAccessible(true) 绕过访问检查,但可能受到模块或安全限制。

Q:为什么 method.invoke 抛的是 InvocationTargetException? A:被调用方法内部抛出的异常会被包装起来,根因在 getTargetException()。

Q:反射一定很慢吗? A:比直接调用慢,但框架通过缓存、方法句柄、字节码生成等方式降低开销。

Q:业务代码应该大量使用反射吗? A:不建议。反射适合框架和通用工具,普通业务逻辑应优先显式调用。

Q:反射能拿到泛型真实类型吗? A:局部变量通常不能,但字段、方法签名、父类声明中的部分泛型信息可以读取。

Q:Spring 为什么需要反射? A:它需要在运行期扫描类、读取注解、创建对象、注入依赖、调用生命周期方法。

Q:模块化后反射有什么变化? A:未开放的模块包可能拒绝深反射访问,需要通过模块声明或 JVM 参数开放。

上次更新: 2026/06/24, 16:28:28
枚举与注解
值传递与引用

← 枚举与注解 值传递与引用→

Copyright © 2023-2026 Wray | 鄂ICP备2024050235号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式