反射的概念

Java 反射(Reflection)是 Java 非常重要的动态特性,通过使用反射我们不仅可以获取到任何类的成员方法(Methods)、成员变量(Fields)、构造方法(Constructors)等信息,还可以动态创建 Java 类实例、调用任意的类方法、修改任意的类成员变量值等。Java 反射机制是 Java 语言的动态性的重要体现,也是 Java 的各种框架底层实现的灵魂。

反射技术,指的是加载类的字节码到内存,并以编程的方法解刨出类中的各个成分(成员变量、方法、构造器等)

获取 Class 对象

反射的第一步:是将字节码加载到内存,我们需要获取到的字节码对象。

以下四种方法可以获取到一个类的字节码文件。

  1. 类名.Class
  2. Class.forName("com.qwesec.xxx.Test")
  3. ClassLoader.getSystemClassLoader().loadClass("com.qwesec.xxx.Test")
  4. 实例化对象通过 对象.getClass()也可以获得
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
public class User {
private String nickname;
private String username;
private String password;

public User() {

}

private User(String password) {
this.password = password;
}

public User(String nickname, String username, String password) {
this.nickname = nickname;
this.username = username;
this.password = password;
}

public String getNickname() {
return nickname;
}

public void setNickname(String nickname) {
this.nickname = nickname;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ReflecTest1 {
public static void main(String[] args) throws ClassNotFoundException {
// 1. 类名.class
Class<User> userClass1 = User.class;
System.out.println(userClass1);
System.out.println(userClass1.getSimpleName()); // 获取全类名
System.out.println(userClass1.getName()); // 获取类名

// 2. Class.forName("className")
Class<?> userClass2 = Class.forName("com.qwesec.User");
System.out.println(userClass2);

// 3. ClassLoader.getSystemClassLoader().loadClass("className")
Class<?> userClass3 = ClassLoader.getSystemClassLoader().loadClass("com.qwesec.User");
System.out.println(userClass3);

// 4. 实例化对象 通过 对象.getClass 方法获取
User user = new User();
Class<?> userClass4 = user.getClass();
System.out.println(userClass4);
}
}

运行结果:

获取类的构造器

获取类的构造器:

获取构造器的作用:依然是初始化对象返回

获取类的所有构造器

可以通过getDeclaredConstructors()方法获取类的所有构造器。

首先准备一个用于被获取构造器的 User 类。

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
public class User {
private String nickname;
private String username;
private String password;

public User() {

}

private User(String password) {
this.password = password;
}

public User(String nickname, String username, String password) {
this.nickname = nickname;
this.username = username;
this.password = password;
}

public String getNickname() {
return nickname;
}

public void setNickname(String nickname) {
this.nickname = nickname;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

写一个测试类,获取 User 类的所有构造器:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReflectTest2 {
public static void main(String[] args) throws ClassNotFoundException {
// 1. 获取 User 类的 Class 对象
Class aClass = Class.forName("com.qwesec.User");
// 2. 获取该类的所有构造方法
Constructor[] constructors = aClass.getDeclaredConstructors();
// 3. 遍历数据中的每一个构造器对象
for (Constructor constructor : constructors) {
System.out.println("构造器名称: " + constructor.getName() + "参数个数: " + constructor.getParameterCount());
}
}
}

运行结果如下, 此时就将 User 类的无参构造和有参构造获取出来了。

获取类的单个构造器

可以通过 getConstructor()方法获取类的单个构造器(仅能获取 public 修饰的构造器)。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReflectTest3 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
// 1. 获取 User 类的 Class 对象
Class aClass = Class.forName("com.qwesec.User");
// 2. 获取该类的无参构造器
Constructor constructor1 = aClass.getConstructor();
// 3. 获取该类的有参构造器
Constructor constructor2 = aClass.getConstructor(String.class, String.class, String.class);
// 4. 输出打印
System.out.println(constructor1);
System.out.println(constructor2);
}
}

输出结果如下:

获取类的私有构造器

可以通过getDeclaredConstructor()方法获取类的某个私有构造器。

1
2
3
4
5
6
7
8
9
public class ReflectTest4 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
// 1. 获取 User 类的 Class 对象
Class aClass = Class.forName("com.qwesec.User");
// 2. 获取该类的私有构造器
Constructor constructor1 = aClass.getDeclaredConstructor(String.class);
System.out.println(constructor1);
}
}

反射获取类的构造器作用

通过反射获取类的构造器作用是为了初始化对象并返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReflectTest5 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1. 获取 User 类的 Class 对象
Class aClass = Class.forName("com.qwesec.User");
// 2. 获取该类的 public 修饰的有参构造器
Constructor constructor = aClass.getConstructor(String.class, String.class, String.class);
// 3. 实例化对象 等同于 new User("熊先生", "x1ongsec", "123456");
User user = (User) constructor.newInstance("熊先生", "x1ongsec", "123456");
System.out.println("昵称: " + user.getNickname());
System.out.println("用户名: " + user.getUsername());
System.out.println("密码: " + user.getPassword());
}
}

运行效果如下:

如果获取的是通过 private 修饰的构造器,则通过 newInstance() 函数实例化前,需要先讲 setAccessible(true) 设置为 true,表示禁止检查访问限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReflectTest6 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1. 获取 User 类的 Class 对象
Class aClass = Class.forName("com.qwesec.User");
// 2. 获取该类的 private 修饰的有参构造器
Constructor constructor = aClass.getDeclaredConstructor(String.class);
// 3. 禁止检查访问限制
constructor.setAccessible(true);
// 4. 实例化对象
User user = (User) constructor.newInstance("123456");
System.out.println("密码: " + user.getPassword());
}
}

如果在调用私有构造方法之前没有关闭访问检查,则会出现如下错误:

获取和使用类的成员变量

Class 提供了从类中获取成员变量的方法。

获取到成员变量的作用:依然是赋值、取值。

这里为了演示我们在 User 类中添加几个公有的属性。

1
2
3
4
5
6
7
8
9
public class User {
private String nickname;
private String username;
private String password;
public String age;
public String gender;

// getter 和 setter
}

获取所有公有成员变量

1
2
3
4
5
6
7
8
9
10
11
public class ReflectTest7 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1. 获取 User 类的 Class 对象
Class aClass = Class.forName("com.qwesec.User");
// 2. 获取 User 类的所有 public 修饰的属性
Field[] fields = aClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
}
}

获取所有成员变量

1
2
3
4
5
6
7
8
9
10
11
public class ReflectTest7 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1. 获取 User 类的 Class 对象
Class aClass = Class.forName("com.qwesec.User");
// 2. 获取 User 类的所有属性(只要存在)
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
}
}

获取和操作指定的成员变量

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
public class ReflectTest8 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
// 1. 获取 User 类的 Class 对象
Class aClass = Class.forName("com.qwesec.User");

// 2. 获取 User 类的无参构造器
Constructor constructor = aClass.getConstructor();
User user = (User) constructor.newInstance();

// 3. 获取 User 类的私有 nickname 属性
Field nickname = aClass.getDeclaredField("nickname");
// 4. 关闭访问检查
nickname.setAccessible(true);
// 5. 设置 nickname 属性
nickname.set(user, "熊先生");

// 6. 设置其他私有属性的值
Field username = aClass.getDeclaredField("username");
username.setAccessible(true);
username.set(user, "x1ongsec");

Field password = aClass.getDeclaredField("password");
password.setAccessible(true);
password.set(user, "123456");

// 7. 设置公有属性的值无需关闭访问检查
Field gender = aClass.getDeclaredField("gender");
gender.set(user, "男");

Field age = aClass.getDeclaredField("age");
age.set(user, "18");

// 7. 打印相关信息
for (Field declaredField : aClass.getDeclaredFields()) {
// 关闭访问控制检查
declaredField.setAccessible(true);
System.out.println(declaredField.get(user));
}
}
}

执行结果如下:

获取类的成员方法

Class 提供了从类中获取成员方法的 API。

成员方法的作用:依然是方法的执行。

在 User 中添加几个方法用于测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void login() {
System.out.println("用户登录了");
}

public void login(String username) {
System.out.println("用户" + username + "登录了");
}

private void logout() {
System.out.println("用户注销了");
}

private void logout(String username) {
System.out.println("用户" + username + "注销了");
}

获取所有公有方法

1
2
3
4
5
6
7
8
9
10
11
public class ReflectTest9 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1. 获取 User 类的 Class 对象
Class aClass = Class.forName("com.qwesec.User");
// 2. 获取 User 类的所有 public 修饰的方法
Method[] methods = aClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
}
}

运行结果如下:

可以发现除了我们自己定义的方法以外,其中还有父类对象 Object 的公有方法,这是因为,在 Java 中默认的所有类都继承自父类 Object。

获取所有方法

getDeclaredMethods() 仅能获取自身的(public、private、protected)所有方法,不能获取父类的。

1
2
3
4
5
6
7
8
9
10
11
public class ReflectTest10 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1. 获取 User 类的 Class 对象
Class aClass = Class.forName("com.qwesec.User");
// 2. 获取 User 类的所有方法
Method[] methods = aClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}
}
}

获取和操作指定的成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ReflectTest11 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1. 获取 User 类的 Class 对象
Class aClass = Class.forName("com.qwesec.User");

// 2. 获取对象实例
User user = (User) aClass.newInstance();

// 3. 获取 User 类的 login 无参方法(公有)
Method login1 = aClass.getMethod("login");
login1.invoke(user);

// 4. 获取 User 类的 login(String username) 方法(公有)
Method login2 = aClass.getMethod("login", String.class);
login2.invoke(user, "x1ongsec");

// 5. 获取 User 类的 logout(String username) 方法(私有)
Method logout1 = aClass.getDeclaredMethod("logout", String.class);
// 6. 禁止访问权限检查
logout1.setAccessible(true);
logout1.invoke(user, "x1ongsec");
}
}

命令执行

不使用反射命令执行

1
2
3
4
5
public class ExecCmd {
public static void main(String[] args) throws IOException {
Runtime.getRuntime().exec("Open -a Calculator");
}
}

使用反射调用 Runtime 执行 exec

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
public class ReflectExecCmd {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, InterruptedException {
// 1. 获取类构造器
Class<?> runtimeC = Class.forName("java.lang.Runtime");
// 2. 获取构造方法
Constructor<?> runtimeConstructor = runtimeC.getDeclaredConstructor();
// 3. 禁止检查访问限制
runtimeConstructor.setAccessible(true);
// 4. 创建 Runtime 实例,等同于 new Runtime()
Runtime runimeInstance = (Runtime) runtimeConstructor.newInstance();
// 5. 获取 Runtime的 exec(String cmd) 方法
Method execCmd = runtimeC.getMethod("exec", String.class);
// 6. 调用 exec()方法
Process p = (Process) execCmd.invoke(runimeInstance, "whoami");
// 6. 读取命令输出结果(关键步骤)
InputStream inputStream = p.getInputStream(); // 命令的输出流

BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
System.out.println("命令输出:" + line); // 打印结果
}

// 7. 等待命令执行完成并释放资源
p.waitFor();
reader.close();
inputStream.close();

}
}

Java 反射机制总结

Java 反射机制是 Java 动态性中最为重要的体现,利用反射机制我们可以轻松实现 Java 类的动态调用。Java 的大部分框架均基于反射机制实现(如:Spring MVCORM框架 等),同时 Java 反射在漏洞利用代码编写、代码审计、绕过 RASP 方法限制等场景中也起到了至关重要的作用。

参考文章