Java反射

Reflection (反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法

Java 反射机制提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在 运行时处理注解
  • 生成动态代理

反射相关的主要API

  • java lang Class 代表一 个类
  • java lang reflect Method代表类的方法
  • java lang reflect Field代表类的成员变量
  • java lang reflect Constructor代表类的构造器

反射的例子

通过反射创建对象、调用公有的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Class clazz = User.class;       // 获取Class对象

// 通过反射创建Person类的对象
Constructor constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("admin", 1);
System.out.println(obj);

// 通过反射调用对象的属性和方法
// 调用属性
Field name = clazz.getDeclaredField("name");
name.set(obj, "Tom");
System.out.println(obj);

// 调用方法
Method setName = clazz.getDeclaredMethod("setName", String.class);
setName.invoke(obj, "Bob");
System.out.println(obj);

通过反射还可以调用私有的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 // 调用私有的构造器
Constructor constructor1 = clazz.getDeclaredConstructor(String.class);
constructor1.setAccessible(true); // 设置权限为可访问
Object obj1 = constructor1.newInstance("Test");
System.out.println(obj1);

// 调用私有的属性
Field id = clazz.getDeclaredField("id");
id.setAccessible(true);
id.set(obj1, 1);
System.out.println(obj1);

// 调用私有的方法
Method show = clazz.getDeclaredMethod("show");
show.setAccessible(true);
show.invoke(obj1);

关于java.lang.Class类

类的加载过程:
使用java命令对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中,这个过程就是类的加载。
对于加载到内存中的类,称为运行时类,这个运行时类就作为java.lang.Class的一个实例。

对于每个类而言, JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构class/interface/enum/annotation/primitive type/void/[]的有关信息 。

  • Class本身也是一个类
  • Class 对象只能由系统建立对象
  • 一个加载的类在JVM中只会有一个Class实例
  • 一 个Class 对象对应的是一个加载到 JVM中的一个class文件
  • 每个 类的实例都会记得自己是由哪个Class 实例所生成,通过Class可以完整地得到一个类中的所有被加载的结构
  • Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class 对象

Class类的常用方法:

1
2
3
4
5
6
7
8
9
10
static Class forName(String name);    // 返回指定类名name的Class对象
Object newInstance(); // 调用缺省构造函数,返回该Class对象的一个实例
getName(); // 返回此Class对象所表示的实体名称
Class getSuperClass(); // 返回当前Class对象的弗雷的Class对象
Class[] getInterfaces(); // 获取当前Class对象的接口
ClassLoader getClassLoader(); // 返回该类的加载器
Class getSuperclass(); // 返回表示此Class所表示的实体的超类的Class
Constructor[] getConstructors(); // 返回一个包含某些Constructor对象的数组
Field[] getDeclaredFields(); // 返回Field对象的一个数组
Method getMethod(String name, Class ... paramTypes); // 返回一个Method对象,此对象的形参类型为paramType

获取Class 类的实例(四种方法):

  1. 若已知具体的类,通过类的class属性获取, 该方法最为安全可靠,程序性能最高
    1
    Class clazz = String.class;
  2. 若已知某个类的实例,调用该实例的getClass方法获取 Class 对象
    1
    Class clazz = "test".getClass();
  3. 已知一个类的全类名,且该类在类路径下,可通过Class 类的静态方法forName获取,可能抛出ClassNotFoundException
    1
    Class clazz = Class.forName(java.lang.String);
  4. 使用classLoader来获取
    1
    2
    ClassLoader classloader = this.getClass().getClassLoader();
    Class clazz = classloader.loadClass("java.lang.String");

类的加载与ClassLoader

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化

  • 类的加载(Load)
    将类的class 文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成
  • 类的链接(Link)
    将类的二进制数据合并到 JRE 中
  • 类的初始化(Initialize)
    JVM负责对类进行初始化

类加载器的作用:

  • 类加载的作用: 将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象作为方法区中类数据的访问入口 。
  • 类缓存: 标准的 JavaSE 类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间 。 不过 JVM 垃圾回收机制可以回收这些Class对象 。

JVM 规范 定义 了如下类型的类的加载器。

  • 引导类加载器(Boostap Classloader)
    用 C++编写的,是 JVM 自带的类加载器, 负责 Java 平台核心库,用来装载核心类库。该加载器无法直接获取
  • 扩展类加载器(Extension Classloader)
    负责jre/lib/ext目录下的 jar包或-D java.ext.dirs指定目录下的 jar 包装入工作库
  • 系统类加载器(System Classloader)
    负责 java classpath-D java.class.path所指的目录下的类与 jar 包装入工作,是最常用的加载器
1
2
3
4
5
6
7
8
 // 获取一个系统类加载器
ClassLoader classloader = ClassLoader.getSystemClassLoader(); // AppClassLoader

// 获取系统类加载器的父类加载器,即扩展类加载器
classloader = classloader.getParent(); // PlatformClassLoader

// 获取扩展类加载器的父类加载器,即引导类加载器
classloader = classloader.getParent(); // null

获取运行时类的完整结构

获取类的属性及其结构

1
2
3
4
5
6
7
8
Class clazz = String.class;
Field[] fields = clazz.getFields(); // 可以获取类中声明为public访问权限的属性
Field[] dFields = clazz.getDeclaredFields(); // 可以获取类中所有权限的属性

Field f = fields[0];
System.out.println(Modifier.toString(f.getModifiers())); // 属性的权限
System.out.println(f.getType()); // 属性的类型
System.out.println(f.getName()); // 属性的名字

获取类的方法及其结构

1
2
3
4
5
6
7
8
9
Class clazz = String.class;
Class clazz = String.class;
Method[] methods = clazz.getMethods();
Method[] dMethods = clazz.getDeclaredMethods();

Method m = methods[0];
System.out.println(m.getName());
System.out.println(m.getModifiers());
System.out.println(m.getReturnType());

获取类的构造器结构

1
2
3
Class clazz = String.class;
Constructor[] constructors = clazz.getConstructors();
Constructor[] dconstructors = clazz.getDeclaredConstructors();

创建运行时类的对象

获得了类的Class对象之后,可以使用这个Class对象来创建对象。
使用Class的静态方法newInstance可以创建一个对象,其内部调用了类的空参构造器。

1
2
Class clazz = String.class;
Object o = clazz.newInstance();

除此之外可以先获取类的构造器,再使用类的构造器来创建对象

1
2
3
Class clazz = String.class;
Constructor constructor = clazz.getDeclaredConstructor();
Object o = constructor.newInstance();

调用运行时类的指定结构

调用指定的属性

1
2
3
4
5
6
7
Class clazz = User.class;
Object obj = clazz.newInstance();

Field name = clazz.getDeclaredField("name"); // 获取指定属性名的属性
name.setAccessible(true); // 设置权限可访问
name.set(obj, "admin"); // 设置属性的值。参数1:指明设置那个对象的属性,参数2:将此属性值设置为多少
System.out.println(name.get(obj)); // 获取属性的值

调用指定的方法

1
2
3
4
5
6
7
8
9
Class clazz = User.class;
Object obj = clazz.newInstance();

// 获取指定名字的方法,第二个及其之后的参数表明指定参数类型的方法,用于筛选重载的方法
Method show = clazz.getDeclaredMethod("setName", String.class);
show.setAccessible(true); // 设置权限可访问
Object ret = show.invoke(obj, "test"); // 调用方法,参数1指明设置那个对象的属性,参数2表明传递的参数
// 其返回值为调用方法的返回值
// 若方法为静态方法,则参数1传入类的Class对象或者传入null

参考

P神的《Java安全漫谈》——反射篇
https://javasec.org/javase/Reflection/Reflection.html
尚硅谷Java入门视频教程——反射部分

文章作者: Dar1in9
文章链接: http://dar1in9s.github.io/2022/09/10/Java/反射/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Dar1in9's Blog