Skip to content

反射 reflect

Java 反射机制是 Java 的核心特性之一,它提供了一种在运行时动态获取和操作类信息的能力,实现灵活的动态编程。

1. 反射的核心实现原理

  1. 类加载与 Class 对象
  • JVM 在类加载阶段(Loading)会通过类加载器(ClassLoader)生成一个 java.lang.Class 对象,这个对象记录了类的元数据信息(成员变量、方法、构造方法等)。
  • 所有类的 Class 对象都是单例,并且是反射操作的入口。
  1. 反射的底层实现
  • 通过 JNI(Java Native Interface)调用本地方法,例如 Class.getDeclaredMethods() 最终会调用 JVM 内部的 C++ 实现。
  • 反射操作通过动态解析类的元数据缓存(存储在方法区),避免重复解析以提高性能。

Class 类的理解

  • 针对已编写好的.java 源文件进行编译 (使用 javac.exe),生成对应的.class 文件。
  • 接着,我们使用 java.exe 命令对指定的 .class 文件进行解释运行。
  • 这个解释运行的过程中,我们需要将 .class 字节码文件加载(使用类的加载器)到内存中(存放在方法区)。加载到内存中的 .class 文件对应的结构就是 Class 的一个实例。

比如:加载到内存中的 Person 类或 String 类,都作为 Class 的一个一个的实例。

java
//1、调用运行时类的静态属性
Class clazz1 = Person.class; //运行时类

//2、调用运行时类的对象的 getClass()
Person person = new Person();
Class clazz2 = person.getClass();

//3、Class.forName("全类名")
Class clazz3 = Class.forName("com.ttdxg.reflect.Person");

//4、类的加载器
Class<?> clazz4 = ClassLoader.getSystemClassLoader().loadClass("com.ttdxg.reflect.Person");

说明:运行时类在内存中会缓存起来,在整个执行期间,只会加载一次 (更准确的说,同一个类只能被同一个类加载器加载一次)

类的加载过程

  1. 类的装载

将类的 class 文件读入内存,并为之创建一个 java.lang.Class 对象,此过程由类加载器完成

  1. 链接
  • 验证 (Verify):确保加载的类信息符合 JVM 规范,例如:以 cafebabe 开头,没有安全问题
  • 准备 (Prepare):正式为静态变量分配内存并设置默认初始化值,这些内存在方法区中分配
  • 解析 (Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
  1. 初始化

执行类构造器 <clinit>() 方法的过程

类构造器 <clinit>() 方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的

类的加载过程.png

2. 反射 API

2.1 创建运行时类的对象

  • 要求运行时类必须提供一个空参构造器
  • 调用空参构造器的权限必须足够
java
Class clazz = Person.class;

Person person = class.newInstance();

2.2 获取运行时类的完整结构

java
/**
 * 反射可以获取注解信息 权限修饰符 返回值类型 方法名(形参类型1 参数1,。。。。) 异常类型
 */
@Test
public void test3() {
    Class<Person> personClass = Person.class;
    Method[] declaredMethods = personClass.getDeclaredMethods();

    for (Method method : declaredMethods) {
        //获取方法声明的注解
        Annotation[] annotations = method.getAnnotations();

        //权限修饰符
        String s = Modifier.toString(method.getModifiers());

        //返回值类型
        String name = method.getReturnType().getName();

        //形参列表
        Class<?>[] parameterTypes = method.getParameterTypes();

        //异常列表
        Class<?>[] exceptionTypes = method.getExceptionTypes();


        //获取运行时父类
        Class<?> superclass = clazz.getSuperclass();

        //获取带泛型的父类
        Type genericSuperclass = clazz.getGenericSuperclass();

        //获取运行时类实现的接口
        Class<?>[] interfaces = clazz.getInterfaces();

        //获取运行时类所在的包
        Package aPackage = clazz.getPackage();
    }
}

2.3 获取运行时类的父类的泛型 (难点)

java
    //获取带泛型的父类 (Type 是一个接口, Class 实现了此接口)
    Type superclass = clazz.getGenericSuperclass();
    //带泛型的父类可以强转位 ParameterizedType
    ParameterizedType parameterizedType = (ParameterizedType) superclass;
    //获取泛型参数
    Type[] arguments = parameterizedType.getActualTypeArguments();
    //获取泛型参数名称
    String name = ((Class) arguments[0]).getName();

2.4 调用指定的属性

java
Class<Person> clazz = Person.class;
Person person = clazz.newInstance();

//获取运行时类指定名的属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(person,"Tom");
System.out.println(name.get(person));


//如果是静态变量,直接传 null 即可
name.set(null,"Jerry");
System.out.println(name.get(null));

2.5 调用指定的方法

java
//public void annotationMethod(String name) {
//        System.out.println("带有注解和参数的方法:" + name);
//    }
Method method = clazz.getDeclaredMethod("annotationMethod",String.class);
method.setAccessible(true);
method.invoke(person,"Tom");

2.6 调用指定的构造器

java
Class<Person> clazz = Person.class;
//1、获取指定参数的构造器
Constructor<Person> declaredConstructor = clazz.getDeclaredConstructor(String.class, int.class);
//2、确保权限足够
declaredConstructor.setAccessible(true);
//构造器创建实例
Person person = declaredConstructor.newInstance("Tom", 12);

2.7 获取指定的注解

java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    
    String value();
    
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    
    String columnName();
    
    String columnType();
    
}

@Data
@Table(value = "person")
public class Person {

    @Column(columnName = "name", columnType = "varchar(10)")
    private String name;

    @Column(columnName = "age", columnType = "int")
    private int age = 1;
    
}

@Test
public void test1() throws NoSuchFieldException {
    Class<Person> clazz = Person.class;

    Table table = clazz.getDeclaredAnnotation(Table.class);
    System.out.println(table.value());

    Field nameFiled = clazz.getDeclaredField("name");
    Column column = nameFiled.getDeclaredAnnotation(Column.class);
    System.out.println(column.columnName());
    System.out.println(column.columnType());

}

3. 反射实际应用案例

1. 框架开发

  • Spring Framework 利用反射实现依赖注入和动态代理(AOP)
  • Junit 利用反射查找 @Test 注解的方法并执行

2. 动态代理

java
public class DemoProxy implements InvocationHandler {
    //被代理的对象
    private Object target;
    public Object invoke(Object proxy,Method method,Object[] args) {
        return method.invoke(target,args);
    }
}