Java代码审计-反射与类加载机制全解
字数 2120 2025-08-11 17:39:49
Java反射与类加载机制全解
一、反射机制概述
1. 反射的基本概念
反射(Reflection)是Java语言的一种特性,它允许程序在运行时:
- 获取任意类的内部信息
- 操作任意对象的属性和方法
- 动态创建对象和调用方法
2. 反射的核心原理
- 加载类后,在堆中生成一个Class类型的对象(一个类只有一个Class对象)
- 该对象包含类的完整结构信息,像一面镜子反映类的结构
- 通过这个Class对象可以获取类的构造器、方法和属性
3. 反射的主要功能
- 运行时判断任意对象所属的类
- 运行时构造任意类的对象
- 运行时获取任意类的成员变量和方法
- 运行时调用任意对象的成员变量和方法
- 生成动态代理
二、反射核心类
1. 主要反射类
| 类名 | 作用 |
|---|---|
| java.lang.Class | 代表一个类,Class对象表示某个类加载后在堆中的对象 |
| java.lang.reflect.Method | 代表类的方法 |
| java.lang.reflect.Field | 代表类的成员变量 |
| java.lang.reflect.Constructor | 代表类的构造方法 |
2. Class类详解
获取Class对象的6种方式
-
Class.forName("全类名") - 通过配置文件读取类全路径
Class cls1 = Class.forName("java.lang.Cat"); -
类名.class - 最安全可靠,性能最高
Class cls2 = Cat.class; -
对象.getClass() - 通过已有对象获取
Class clazz = 对象.getClass(); -
通过类加载器
ClassLoader cl = 对象.getClass().getClassLoader(); Class clazz4 = cl.loadClass("类的全类名"); -
基本数据类型.class
Class<Integer> integerClass = int.class; -
包装类.TYPE
Class<Integer> type = Integer.TYPE;
Class类的常用方法
| 方法 | 说明 |
|---|---|
| static Class forName(String name) | 返回指定类名的Class对象 |
| Object newInstance() | 调用缺省构造函数创建实例 |
| String getName() | 返回类全名 |
| Class getSuperclass() | 返回父类的Class对象 |
| Class[] getInterfaces() | 返回实现的接口 |
| Field[] getFields() | 返回所有public属性 |
| Method[] getMethods() | 返回所有public方法 |
| Constructor[] getConstructors() | 返回所有public构造器 |
三、反射的基本使用
1. 反射创建对象
方式一:调用无参构造器
Class<?> cls = Class.forName("com.hspedu.Cat");
Object o = cls.newInstance();
方式二:调用指定构造器
// 获取有参构造器
Constructor<?> constructor = userClass.getConstructor(String.class);
// 创建实例
Object xl = constructor.newInstance("小李");
访问私有构造器
// 获取private构造器
Constructor<?> constructor1 = userClass.getDeclaredConstructor(String.class, int.class);
// 爆破(暴力访问)
constructor1.setAccessible(true);
// 创建实例
Object zsf = constructor1.newInstance("张三丰", 100);
2. 反射操作属性
// 1. 获取属性对象
Field age = stuClass.getField("age"); // public属性
Field name = stuClass.getDeclaredField("name"); // private属性
// 2. 爆破私有属性
name.setAccessible(true);
// 3. 设置属性值
age.set(o, 88);
name.set(o, "李四");
// 4. 获取属性值
Object value = age.get(o);
// 静态属性操作
name.set(null, "静态值");
3. 反射调用方法
// 1. 获取方法对象
Method hi = cls.getMethod("hi"); // public方法
Method privateMethod = cls.getDeclaredMethod("privateMethod"); // private方法
// 2. 爆破私有方法
privateMethod.setAccessible(true);
// 3. 调用方法
hi.invoke(o);
privateMethod.invoke(o);
// 静态方法调用
staticMethod.invoke(null);
四、反射性能优化
1. 反射性能问题
- 反射是解释执行,比直接调用效率低
- 安全检查也会影响性能
2. 优化方法
使用setAccessible(true)关闭访问检查:
Method hi = cls.getMethod("hi");
hi.setAccessible(true); // 关闭访问检查
long start = System.currentTimeMillis();
for (int i = 0; i < 9000000; i++) {
hi.invoke(o);
}
long end = System.currentTimeMillis();
3. 性能对比
| 调用方式 | 执行时间(900万次) |
|---|---|
| 直接调用 | 约5ms |
| 反射调用 | 约200ms |
| 反射+关闭检查 | 约100ms |
五、类加载机制
1. 类加载时机
- 创建对象时(new)
- 子类被加载时
- 调用类中的静态成员时
- 通过反射
2. 类加载方式
静态加载
编译时加载相关类,没有则报错
Dog dog = new Dog(); // 必须编写Dog类
动态加载
运行时加载需要的类,运行时不用该类则不报错
Class cls = Class.forName("Person"); // 运行时才检查
Object o = cls.newInstance();
Method m = cls.getMethod("hi");
m.invoke(o);
3. 类加载过程
-
加载(Loading)
- 将字节码加载到内存
- 生成java.lang.Class对象
-
连接(Linking)
- 验证(Verification):确保Class文件符合规范
- 准备(Preparation):为静态变量分配内存并初始化默认值
- 解析(Resolution):将符号引用转为直接引用
-
初始化(Initialization)
- 执行类构造器
<clinit>()方法 - 按顺序收集静态变量赋值和静态代码块
- 线程安全,保证只有一个线程执行初始化
- 执行类构造器
六、反射获取类结构信息
1. 获取类信息
// 获取全类名
cls.getName();
// 获取简单类名
cls.getSimpleName();
// 获取包名
cls.getPackage().getName();
// 获取父类
cls.getSuperclass();
// 获取接口
cls.getInterfaces();
// 获取注解
cls.getAnnotations();
2. 获取字段信息
Field[] fields = cls.getFields(); // 所有public字段(包括父类)
Field[] declaredFields = cls.getDeclaredFields(); // 本类所有字段
for (Field field : declaredFields) {
// 字段名
field.getName();
// 修饰符(int表示)
field.getModifiers();
// 字段类型
field.getType();
}
3. 获取方法信息
Method[] methods = cls.getMethods(); // 所有public方法(包括父类)
Method[] declaredMethods = cls.getDeclaredMethods(); // 本类所有方法
for (Method method : declaredMethods) {
// 方法名
method.getName();
// 修饰符
method.getModifiers();
// 返回类型
method.getReturnType();
// 参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
}
4. 获取构造器信息
Constructor<?>[] constructors = cls.getConstructors(); // public构造器
Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors(); // 所有构造器
for (Constructor<?> constructor : declaredConstructors) {
// 构造器名(全类名)
constructor.getName();
// 参数类型
Class<?>[] parameterTypes = constructor.getParameterTypes();
}
七、反射应用场景
- 框架开发:Spring、Hibernate等框架底层大量使用反射
- 动态代理:AOP编程的基础
- 注解处理:运行时通过反射获取注解信息
- 工具类开发:如BeanUtils、PropertyUtils等
- 单元测试:动态调用测试方法
- 插件系统:动态加载插件类
八、反射的优缺点
优点:
- 动态创建和使用对象,提高灵活性
- 是框架技术的底层支撑
- 可以在运行时操作私有成员
缺点:
- 执行效率较低
- 破坏了封装性
- 可能引发安全问题
九、代码示例
1. 反射创建对象并调用方法
// 1. 加载类
Class<?> cls = Class.forName("com.hspedu.Cat");
// 2. 创建实例
Object o = cls.newInstance();
// 3. 获取方法
Method method1 = cls.getMethod("hi");
// 4. 调用方法
method1.invoke(o);
2. 反射操作私有成员
// 获取私有字段
Field field = cls.getDeclaredField("privateField");
// 爆破
field.setAccessible(true);
// 设置值
field.set(o, value);
// 获取私有方法
Method method = cls.getDeclaredMethod("privateMethod");
method.setAccessible(true);
method.invoke(o);
3. 反射获取类结构
Class<?> cls = Class.forName("com.example.Person");
// 获取所有字段
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
System.out.println("字段名: " + field.getName());
System.out.println("类型: " + field.getType().getSimpleName());
System.out.println("修饰符: " + Modifier.toString(field.getModifiers()));
}
// 获取所有方法
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
System.out.println("方法名: " + method.getName());
System.out.println("返回类型: " + method.getReturnType().getSimpleName());
System.out.println("修饰符: " + Modifier.toString(method.getModifiers()));
}
十、总结
Java反射机制是Java语言动态性的核心体现,它:
- 通过Class类提供运行时类型信息
- 可以动态创建对象、调用方法和访问属性
- 是框架开发的底层基础
- 虽然性能较低但灵活性极高
- 需要合理使用,避免破坏封装性
掌握反射机制对于理解Java高级特性和框架原理至关重要,是Java开发者必须掌握的核心技术之一。