Java反射
Java反射机制
java动态特性:一段代码,改变其中的变量,将会使这段代码产生功能性的变化。
Java反射机制是指在运行状态时,对于任意一个类,都能够获取这个类的所有属性和方法;对于一个对象,都能够调用它的任意一个方法和属性(包括私有方法和属性),这种动态获取信息以及动态调用对象方法的功能就称为Java的反射机制。
通过使用反射我们不仅可以获取到任何类的成员方法(Methods)、成员变量(Fields)、构造方法(Constructors)等信息,还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。
1 | public void execute(String className, String methodName) throws Exception { |
可以看到当我们创建一个类文件后,经过javac编译,就会形成.class文件,同时jvm内存会查找生成的.class文件读入内存和经过ClassLoader加载,同时会创建生成一个Class对象,里面拥有其获取的成员变量,成员方法和构造方法等。
Class类
JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息。如果获取了某个Class实例,就可以通过这个Class实例获取到该实例对应的class的所有信息
如果已经加载了某个类,就可以通过一个class的静态变量class获得。使用**.class来创建Class对象的引用时,不会自动初始化该Class对象**。
1
Class cls = String.class;
未初始化该Class对象,不会触发静态代码块和静态变量赋值。
如果上下文中存在某个类的实例,可以通过该实例变量提供的
getClass()方法获取1
2
3
4
5
6
7
8
9
10
11public class Main {
public static void main(String[] args) throws ClassNotFoundException {
String s="b1uel0n3";
Class cls=s.getClass();
System.out.println(cls.getName()); //获取类名,输出java.lang.String
System.out.println(cls.getSimpleName());
System.out.println(cls == String.class); //检查Class对象是否表示String类,输出true
}
}此时获取的是已知实例的class对象,所以已初始化
知道一个class的完整类名,可以通过静态方法
Class.forName()获取。使用forName()会初始化该Class对象,即会触发静态代码块和静态变量赋值。1
Class cls=Class.forName("java.lang.String");
foaName有两个函数重载:
1 | Class<?> cls=Class.forName(String name) |
Class<?>表示某个未知类型的对象,泛型<?>是为了保证类型安全,避免强制类型转换的风险
1 | Class.forName(className) |
name指定加载的类名,initialize指定是否初始化,loader指定类加载器,若为null,默认使用系统类加载器
第二个参数表示是否初始化,在forName的时候,构造函数并不会执行,而是执行类初始化。他会先执行static{}静态块里的内容:
1 | public class LazyClass { |
1 | public class Main { |
可以看到,当实例化这个类时,首先调用的是静态块static{},其次是实例块{},最后是构造函数。其中static{}就是在类初始化的时候调用的
静态块是类加载时执行一次,实例块是每次实例化时执行,构造函数也是每次实例化时执行
而由于Class实例在JVM中是唯一的,所以,上诉方法获取的Class实例是同一个实例
Field类
访问字段
1 | Field getField(name): 根据字段名获取某个public的field(包括父类) |
Demo:
1 | public class Main { |
1 | public int Person.age |
一个Field对象包含了一个字段的所有信息:
getName():返回字段名称getType():返回字段类型,也是一个Class实例getModifiers():返回字段修饰符get(obj):获取字段值- set:修改字段值
获取字段值
在Java反射机制中,主要通过Field.get(Object obj)来获取字段值,而字段值是存储在对象实例中的,因此我们要通过反射获取或修改某个字段值时,必须明确操作的具体对象实例
1 | public class Main { |
stdClass.getField(“score”)用于获取字段对象,注意stdClass是一个对象而并非实例,所以需要创建一个实例
但我们会发现,上面代码无法获取private字段,会抛出IllegalAccessException错误,这是因为没有访问权限
在访问私有字段前,我们可以通过设置Field.setAccessible(true)强制开启访问权限:
1 | import java.lang.reflect.Field; |
修改字段值
1 | Field f = stdClass.getDeclaredField("grade"); |
修改和读取的实例要相同,
stdClass.newInstance()每次调用都会创建一个新的Student实例。所以不要f.set(f.get(stdClass.newInstance()),50);
如果修改非public字段,需要先调用setAccessible(true)开启访问权限
Method类
获取方法
1 | Method getMethod(name,Class...): 获取某个public的Method(包括父类) |
Demo:
1 | import java.lang.reflect.Method; |
1 | public java.lang.String Person.getName(java.lang.String) |
一个Method对象包含一个方法的所有信息:
getName(): 返回方法名称getReturnType(): 返回方法返回值类型,也是一个Class实例getParameterTypes(): 返回方法的参数类型,是一个Class数组getModifiers(): 返回方法的修饰符
调用方法
invoke:调用方法
invoke用于执行方法,它的第一个参数是:
如果这个方法是一个实例方法,那么第一个参数是实例对象
1
2
3
4
5
6public void sayHello() {
System.out.println("Hello!");
}
Student student = new Student();
Method method = Student.class.getMethod("sayHello");
method.invoke(student);如果第一个方法是静态方法,那么第一个参数是类
这里用substring举个例子:
1 | import java.lang.reflect.Method; |
调用非public方法
同样需要通过getAccessible(true)允许调用
1 | import java.lang.reflect.Method; |
多态
1 | import java.lang.reflect.Method; |
Person类中定义了hello()方法,并且它的子类Student覆写了hello()方法,而尽管m是通过从Person.class获取的Method,由于实际调用时JVM会根据对象的实际类型进行动态绑定。所以作用于Student实例时,同样遵循多态原则,实际调用Student类的hello()方法。
调用构造方法
Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。
通过Class实例获取Constructor的方法如下:
1 | getConstructor(Class...): 获取某个public的Constructor |
调用非public的Constructor时,必须先通过setAccessible(true)设置允许访问。
获取继承关系
获取父类:
1 | Class.getSuperclass() |
1 | Class cls=Student.class.getSuperclass(); |
获取接口interface:
1 | Class.getInterface() |
利用反射命令执行
Runtime
在java.long.Runtime中可以通过exec方法执行本地命令:
1 | Runtime.getRuntime().exec("calc.exe"); |
同样我们也能用反射实现:
1 | import java.lang.reflect.Method; |
Class.newInstance()的作用是创建实例调用这个类的无参构造函数,但执行后发现报错IllegalAccessException,产生的原因可能有两点:
- 使用的类没有无参构造方法
- 使用的无参构造方法是私有的
而其中Runtime类的无参构造方法是私有的,且禁止外部直接通过new实例化:
同时发现Runtime.exec方法有六个重载,这里使用最简单的exec(String command)即可:
可以看到该重载方法只有一个参数且为String类型。
注意到Runtime类有一个getRuntime方法可以获取Runtime对象,且该方法是公有的:
所以我们可以通过使用cls.getMethod("exec", String.class);获取Runtime.exec方法,Runtime.getRuntime()用来获取Runtime对象,从而执行Runtime.exec方法
由于Runtime.getRuntime()是一个静态方法,所以在invoke调用时,第一个参数要传入一个Runtime类:
1 | import java.lang.reflect.Method; |
首先m获取Runtime.exec方法,要调用invoke,则第一个参数为实例对象,而cls.getMethod("getRuntime")获取Runtime.getRuntime方法,通过调用invoke返回Runtime对象作为m.invoke的第一个参数,由于getRuntime为静态方法,所以参数为一个类即cls
还可以利用设置setAccessible(true)修改构造方法访问权限实现暴力访问权限:
1 | import java.io.File; |
ProcessBuilder
ProcessBuilder有两个构造函数:
利用第一种形式的构造函数,我们可以在getConstructor处传入List.class,获取start方法执行:
1 | import java.lang.reflect.Method; |
原理就是由于ProcessBuilder类中存在接受List参数的构造方法,由于Java泛型的类型擦除,反射时List.class对应List<String>、List<Integer>等所有泛型形式。所以通过cls.getConstructor(List.class)来获取第一种形式构造方法,然后再通过获取的构造方法创建实例,这里直接将我们的命令通过Arrays.asList()封装成List对象,然后调用start()方法启动进程执行命令
分解下我们的payload:
1 | import java.lang.reflect.Constructor; |
当然我们也可以利用第二种形式的构造函数,对于可变长参数,Java其实在编译时会编译成一个数组,也就是说,如下两种写法其实是等价的:
1 | public void hello(String[] names){} |
对于反射来说,如果要获取的目标函数里包含可变长参数,我们可以直接认为是数组。
思路还是不变的,通过start进程启动命令即可,我们可将字符串数组的类String[].class传给getConstructor即可,同时注意在创建实例时注意构造函数接收的是可变长参数,而我们传给ProcessBuilder的是一个List<String>类型,二者叠加为一个一个二维数组,如果传一维数组会导致多个String参数报错:
1 | Class clazz = Class.forName("java.lang.ProcessBuilder"); |
由于newInstance接收一个
Object数组,数组每个元素对应构造函数的一个参数,所以newInstance要求参数是object[],写入new String[][]{{"cmd"}}可等效于new Object[]{String[]{"cmd"}}
反射payload:
1 | import java.lang.reflect.Method; |
分解payload:
1 | import java.lang.reflect.Constructor; |
利用反射修改final关键字修饰的成员变量
反射获取Field类的字段修饰符modifiers
1
Field modifiers=field.getClass().getDeclaredField("modifiers");
设置modifiers修改权限
1
modifiers.setAccessible(true);
修改成员变量的Field对象的modifiers值
1
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
修改成员变量值
1
field.set(类实例对象, 修改后的值);
参考
https://www.cnblogs.com/ElloeStudy/p/16065219.html
https://www.javasec.org/javase/Reflection/Reflection.html
https://nivi4.notion.site/Java-8e0ad38312714da48ec396a6544dd3d2#cc9b11b89ca84dbb81065621377306a6













