反射机制

Java反射机制是Java提供的一种在运行时动态获取类信息并操作类或对象的机制。通过反射,可以在程序运行时获取类的构造方法,成员变量,方法等信息,并调用他们。

反射的优点与缺点

  1. 优点:可以动态地创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑
  2. 缺点:使用反射基本是解释执行,对执行速度有影响

反射的核心类

  • Class类:表示类的元数据,表示某个类加载后在堆中的对象,是反射的入口
  • Constructor类:表示类的构造方法
  • Field类:表示类的成员变量
  • Method类:表示类的方法

底层运作

image-20250105105407854

反射的主要功能

  1. 获取类的信息:
    1. 获取类的名称,修饰符,父类,接口,注解等
    2. 获取类的构造方法,成员变量,方法等信息
  2. 动态创建对象:
    1. 通过 Class.newInstance()Constructor.newInstance() 动态创建对象
  3. 动态调用方法:
    1. 通过 Method.invoke() 动态调用对象的方法。
  4. 动态访问和修改字段:
    1. 通过 Field.get()Field.set() 访问和修改对象的字段。
  5. 操作数组:
    1. 通过 Array 类动态创建和操作数组。

反射相关类

  1. java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
  2. java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
  3. java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
  4. java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示某个类的构造方法

常用方法

  1. 获取Class对象
1
2
3
4
5
6
7
8
9
//通过类的全限定名获取Class对象(编译阶段)
//应用场景:多用于配置文件,读取类全路径,加载类
Class.forName("String className")
//通过类字面常量获取Class对象(加载阶段)
//应用场景:多用于参数传递,比如通过反射得到对应地构造器对象
类.class
//通过对象实例获取Class对象(运行阶段)
//应用场景:通过创建好地对象,获取Class对象
对象.getClass()
  1. 获取构造函数
1
2
3
4
5
6
7
8
//获取指定的公共构造函数
getConstructor(Class<?>... parameterTypes)
//获取指定的构造函数(包括私有构造函数)
getDeclaredConstructor(Class<?> parameterTypes)
//获取所有公共构造函数
getConstructors()
//获取所有构造函数(包括私有构造函数)
getDeclaredConstructors()
  1. 获取方法
1
2
3
4
5
6
7
8
//获取指定的公共方法
getMethod(String name, Class<?>... parameterTypes)
//获取指定的方法(包括私有方法)
getDeclaredMethod(String name, Class<?>... parameterTypes)
//获取所有公共方法
getMethods()
//获取所有方法(包括私有方法)
getDeclaredMethods()
  1. 获取字段
1
2
3
4
5
6
7
8
//获取指定的公共字段
getField(String name)
//获取指定的字段(包括私有字段)
getDeclaredField(String name)
//获取所有公共字段
getFields()
//获取所有字段(包括私有字段)
getDeclaredFields()
  1. 调用方法
1
2
//调用方法
method.invoke(Object obj, Object... args)
  1. 创建实例
1
2
3
4
//通过指定的构造函数创建实例
//需要先获得构造方法,然后才能创建实例
Constructor<?> constructor = cls.getConstructor(Class<?>... parameterTypes);
Object instance = constructor.newInstance(Object... initargs);

动态和静态加载

静态加载

定义:静态加载是指在程序启动或初始化时,一次性将所有资源加载到内存中

特点

  1. 启动时加载:程序启动时即加载所有资源。
  2. 内存占用高:所有资源常驻内存,占用较大。
  3. 响应快:资源已预加载,使用时无需等待。
  4. 适用场景:适合资源少、内存充足或对响应速度要求高的场景。

优点

  • 使用资源时无需额外加载,响应迅速。

缺点

  • 启动慢,内存占用高,资源利用率低。

动态加载

定义
动态加载是在程序运行时,按需加载资源。

特点

  1. 按需加载:资源在使用时才加载。
  2. 内存占用低:只加载当前需要的资源,节省内存。
  3. 响应延迟:首次加载资源时可能有延迟。
  4. 适用场景:适合资源多、内存有限或资源不常用的场景。

优点

  • 启动快,内存占用低,资源利用率高。

缺点

  • 首次加载资源时可能有延迟,增加运行时复杂性。

动态加载的使用场景

  • 使用Class.forName()动态加载类,在运行时根据类名加载类,并返回对应的class对象

类加载

类加载是 JVM将类的字节码文件(.class 文件)加载到内存中,并转换为 Class 对象的过程。

image-20250114160834130

类加载的过程

类加载的过程可以分为以下几个阶段:

  1. 加载(Loading)
    • 通过类的全限定名(包名 + 类名)查找字节码文件。
    • 将字节码文件加载到内存中,并生成一个 java.lang.Class 对象。
    • 加载可以由 JVM 自带的类加载器完成,也可以由用户自定义的类加载器完成。
  2. 验证(Verification)
    • 确保加载的字节码文件符合 JVM 规范,防止恶意代码破坏 JVM。
    • 验证内容包括文件格式、元数据、字节码和符号引用等。
  3. 准备(Preparation)
    • 为类的静态变量分配内存,并设置默认初始值(如 0null 等)。
    • 如果是常量(final static),则直接赋值为代码中定义的值。
  4. 解析(Resolution)
    • 将常量池中的符号引用(Symbolic Reference)转换为直接引用(Direct Reference)。
    • 符号引用是类、方法、字段的名称,直接引用是具体的内存地址。
  5. 初始化(Initialization)
    • 执行类的静态代码块(static {})和静态变量的赋值操作。
    • 这是类加载的最后一步,JVM 会保证类的初始化在多线程环境下是线程安全的。

获取类结构信息

  1. 获取Class对象
1
2
3
4
5
6
7
8
9
// 方式 1:通过类名.class
Class<?> clazz = MyClass.class;

// 方式 2:通过对象的 getClass() 方法
MyClass obj = new MyClass();
Class<?> clazz = obj.getClass();

// 方式 3:通过 Class.forName() 动态加载类
Class<?> clazz = Class.forName("com.example.MyClass");
  1. 获取类的结构信息

    1. 获取类的基本信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //获取类名
    String className = clazz.getName();//全限定名(包名+类名)
    String simpleName = clazz.getSimpleName();//简单类名

    //获取包名
    Package pkg = clazz.getPackage();

    /**获取类的修饰符(public,final,absetract等)
    /*public : 1, private : 2, protected : 4, static : 8, final : 16
    /*synchronized : 32, volatile(表示字段是易变的): 64, transient(表示字段不会被序列化): 128, native(表示字段是本地方法): 256
    /*interface : 512, abstract : 1024, strictfp(表示类或者方法使用严格的浮点计算): 2048
    **/
    int modifiers = clazz.getModifiers();
    //将修饰符对应的二进制再转成对应的修饰符
    String modifierStr = Modifier.toString(modifiers);
    1. 获取类的字段(成员变量)信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 获取所有 public 字段(包括父类的 public 字段)
    Field[] publicFields = clazz.getFields();
    // 获取所有字段(包括私有字段,但不包括父类的字段)
    Field[] allFields = clazz.getDeclaredFields();

    for (Field field : allFields) {
    String fieldName = field.getName(); // 字段名
    Class<?> fieldType = field.getType(); // 字段类型
    int fieldModifiers = field.getModifiers(); // 字段修饰符
    String modifierStr = Modifier.toString(fieldModifiers);
    }
    1. 获取类的方法信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 获取所有 public 方法(包括父类的 public 方法)
    Method[] publicMethods = clazz.getMethods();

    // 获取所有方法(包括私有方法,但不包括父类的方法)
    Method[] allMethods = clazz.getDeclaredMethods();

    for (Method method : allMethods) {
    String methodName = method.getName(); // 方法名
    Class<?> returnType = method.getReturnType(); // 返回值类型
    int methodModifiers = method.getModifiers(); // 方法修饰符
    String modifierStr = Modifier.toString(methodModifiers);
    // 获取方法的参数类型
    Class<?>[] parameterTypes = method.getParameterTypes();
    }
    1. 获取类的构造器信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 获取所有 public 构造器
    Constructor<?>[] publicConstructors = clazz.getConstructors();

    // 获取所有构造器(包括私有构造器)
    Constructor<?>[] allConstructors = clazz.getDeclaredConstructors();

    for (Constructor<?> constructor : allConstructors) {
    String constructorName = constructor.getName(); // 构造器名
    int constructorModifiers = constructor.getModifiers(); // 构造器修饰符
    String modifierStr = Modifier.toString(constructorModifiers);

    // 获取构造器的参数类型
    Class<?>[] parameterTypes = constructor.getParameterTypes();

    }
    1. 获取类的父类和接口
    1
    2
    3
    4
    5
    // 获取父类
    Class<?> superClass = clazz.getSuperclass();

    // 获取实现的接口
    Class<?>[] interfaces = clazz.getInterfaces();
  2. 反射操作示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class Main {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//获取Student类的Class对象
Class<Student> stdCls = Student.class;
//获取Student类的有参构造器
Constructor<Student> declaredConstructor = stdCls.getDeclaredConstructor(String.class, int.class);
//通过有参构造器实例化对象
Student student1 = declaredConstructor.newInstance("xiaomi", 20);
//获取Student类的无参构造器
Constructor<Student> constructor = stdCls.getConstructor();
//通过无参构造器实例化对象
Student student = constructor.newInstance();

//获取Student类的name属性
Field name = stdCls.getField("name");
//设置student对象的name属性值
name.set(student, "xiaoming");
//获取Student类的age属性
Field age = stdCls.getDeclaredField("age");
//由于age是private修饰的,需要设置可访问性
age.setAccessible(true);
//设置student对象的age属性值
age.set(student, 30);

//获取Student类的hi方法并调用
stdCls.getMethod("hi").invoke(student);
//获取Student类的privateMethod方法
Method privateMethod = stdCls.getDeclaredMethod("privateMethod", String.class, int.class);
//由于privateMethod是private修饰的,需要设置可访问性
privateMethod.setAccessible(true);
//调用privateMethod方法
int res = (int)privateMethod.invoke(student, "xiaomi", 30);
}
}

class Student {
public String name;
private int age;

public Student(){}

public Student(String name, int age) {
this.name = name;
this.age = age;
}

public void hi() {
System.out.println("hi");
}

private int privateMethod(String name, int age) {
++this.age;
return age;
}

}

暴破

暴破指的是通过反射或其他技术手段绕过访问控制(如访问私有字段或方法),或者通过修改类的字节码来实现一些非常规操作。

  1. 反射暴破

通过反射可以访问和修改类的私有成员(字段、方法、构造器等),如果不使用暴破只能访问私有变量,而不能修改私有变量。