获取类的方式

  • .class

根据类名,获取类的字面量引用,不会触发类加载

  • .getClass()

类已经被加载,已经实例化了该对象的情况下,根据类名获取类的信息,故该方法不会触发类加载过程

  • .forName()

根据类的全额限定名,寻找到该类,然后会触发类的加载过程(包括加载→链接→初始化)。注意调用forName并不会触发构造方法

代码调试

  • TestReflect.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package org.javasec.FirstReflect;
import static java.lang.Class.forName;

public class TestReflect {
static void test() throws ClassNotFoundException {
System.out.println("===.class-开始===");
Class<User> c1 =User.class;
System.out.println("===.class-结束===");

System.out.println("===getClass-开始===");
User user1 = new User();
Class<? extends User> c2 =user1.getClass();
System.out.println("===getClass-结束===");

System.out.println("===forName-开始===");
Class<?> c3 = forName("org.javasec.FirstReflect.User");
System.out.println("===forName-结束===");


}
}

我们先注释掉其他的操作,仅仅看.class,输出

1
2
3
===.class-开始===
org.javasec.FirstReflect.User
===.class-结束===

确定证明了.class不会触发类加载

然后仅仅看getClass,输出

1
2
3
4
5
6
7
System.out.println("===getClass-开始===");
System.out.println("=======创建对象-开始===");
User user1 = new User();
System.out.println("=======创建对象-结束===");
Class<? extends User> c2 =user1.getClass();
System.out.println(c2.getName());
System.out.println("===getClass-结束===");
1
2
3
4
5
6
7
8
===getClass-开始===
=======创建对象-开始===
[User]: 静态初始化块-执行成功
[User]: 实例初始化-执行成功
[User]: 无参构造方法-执行成功
=======创建对象-结束===
org.javasec.FirstReflect.User
===getClass-结束===

可以看出getClass本身也是不会触发类加载的,而是直接用已经加载的类

再看forName,输出

1
2
3
4
System.out.println("===forName-开始===");
Class<?> c3 = forName("org.javasec.FirstReflect.User");
System.out.println(c3.getName());
System.out.println("===forName-结束===");
1
2
3
4
===forName-开始===
[User]: 静态初始化块-执行成功
org.javasec.FirstReflect.User
===forName-结束===

可以看出只有forName触发了类加载机制,直接调用了静态块的方法!这是一个很重要的特性

如果静态块里面有危险方法,那么攻击者无需拿到类的实例

1
2
3
4
5
6
7
8
9
static {
System.out.println("[User]: 静态初始化块-执行成功");

try {
Process p1 =Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}

仅仅调用forName就能触发危险代码执行!

forName重载

1
2
3
// 三个参数的方法可以控制是否初始化和使用哪个ClassLoader
Class.forName("com.example.MyClass", false, classLoader); // 不初始化
Class.forName("com.example.MyClass", true, classLoader); // 初始化(默认)

默认会进行初始化