Java反射
2022-01-07 00:32:00 # Java

反射概述

  • 反射是指对于任何一个Class类,在”运行的时候”都可以直接得到这个类的全部成分。

  • 在运行时,可以直接得到这个类的构造器对象: Constructor

  • 在运行时,可以直接得到这个类的成员变量对象: Field

  • 在运行时,可以直接得到这个类的成员方法对象: Method

  • 这种运行时 动态获取类信息以及动态调用类中成分的能力称为Java语言的反射机制。

反射的关键

反射的第一步都是先得到编译后的Class类对象,然后就可以得到Class的全部成分。

1
2
HelloWorld.java -> javac ->HelloWorld.class
Class c = HelloWorld.class;

1、反射的基本作用、关键?

  • 反射是在运行时候获取类的字节码文件对象: 然后可以解析类中的全部成分。
  • 反射的核心思想和关键就是: 得到编译后得class文件对象。

1645363880213.png

反射获取类对象

1645423255156.png

获取Class对象的方式:

  • Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象
  • 类名.class:通过类名的属性class获取
  • 对象.getClass():getClass()方法在Object类中定义着

同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个

Class对象功能:

​ 获取功能:

​ 1.获取成员变量

​ Field[] getFields()

​ Field getField(String name)

​ Field[] getDeclareFields()

​ Field getDeclareField(String name)

​ 2.获取构造方法

​ Constructor<?>[] getConstructors()

​ Constructor getConstructors(类<?>… parameterTypes)

​ Constructor getDeclareConstructor(类<?>… parameterTypes)

​ Constructor<?>[] getDeclareConstructors()

​ 3.获取成员方法

​ Method[] getMethods()

​ Method getMethod(String name,类<?>… parameterTypes)

​ Method[] getDeclareMethods()

​ Method getDeclareMethod(String name,类<?>… parameterTypes)

​ 4.获取类名

​ String getName()

getMethod

  • 获取类的⽅法: forName

  • 实例化类对象的⽅法: newInstance

  • 获取函数的⽅法: getMethod

  • 执⾏函数的方法: invoke

Class.forName 如果你知道某个类的名字,想获取到这个类,就可以使⽤ forName 来获取

如果直接执行这段代码的话是错误的,因为Runtime 类的构造方法是私有的

1
2
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.newInstance(), "id");

1641487461549.png

只能通过 Runtime.getRuntime() 来获取到 Runtime 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package Le1a;
import java.lang.reflect.Method;

public class day1 {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("java.lang.Runtime"); //加载java.lang.Runtime类
Method execMethod = clazz.getMethod("exec", String.class);
//用getMethod("exec", String.class) 来获取 Runtime.exec方法
Method getRuntimeMethod = clazz.getMethod("getRuntime");
//getMethod("getRuntime")来获取 Runtime.getRuntime方法
Object runtime = getRuntimeMethod.invoke(clazz);//invoke执行getRuntime方法,参数是Runtime类,也就获得了Runtime对象
execMethod.invoke(runtime, "calc.exe");
}
}

1641487608128.png

利用反射获取构造器对象

1645430132328.png

getConstructor

getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载, 所以必须用参数列表类型才能唯一确定一个构造函数

public ProcessBuilder(Listcommand)

获取到构造函数后,我们使用 newInstance 来执行。 比如,我们常用的另一种执行命令的方式ProcessBuilder,我们使用反射来获取其构造函数,然后调用 start() 来执行命令:

1
2
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();

public ProcessBuilder(String… command)

这个构造函数用到了可变长参数(varargs),在编译的时候会编译成一个数组,如下两种写法在底层是等价的

1
2
public void hello(String[] names) {} 
public void hello(String...names) {}

String[].class 传给 getConstructor ,获取 ProcessBuilder 的第二种构造函数:

1
Class clazz = Class.forName("java.lang.ProcessBuilder"); clazz.getConstructor(String[].class)

在调用 newInstance 的时候,因为这个函数本身接收的是一个可变长参数,我们传给 ProcessBuilder 的也是一个可变长参数,二者叠加为一个二维数组,所以整个Payload如下:

1
2
3
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new
String[][]{{"calc.exe"}})).start();

getDeclared

如果一个方法或构造方法是私有方法,就要用到getDeclared 系列的反射,通过setAccessible(true)暴力反射来破坏封装性

1
2
3
4
5
6
7
8
9
10
11
12
13
package Le1a;

import java.lang.reflect.Constructor;

public class day3 {
public static void main(String[] args) throws Exception{
Class calc = Class.forName("java.lang.Runtime");
Constructor m = calc.getDeclaredConstructor();//这里获取到runtime的构造器对象
m.setAccessible(true);//暴力反射
calc.getMethod("exec", String.class).invoke(m.newInstance(), "calc.exe");

}
}