本文最后更新于 133 天前,其中的信息可能已经有所发展或是发生改变。
Java反射是一个强大的特性,允许程序在运行时查询,访问和修改类,接口,字段和方法的信息
反射提供了一种动态操作类的能力,通过这种机制能够在运行时获得类的信息,并且运行时修改类的结构,调用类的方法,实例化对象等
先来个例子
import java.util.*;
import java.lang.reflect.*;
class Dog {
public void cry() {
System.out.println("汪汪!");
}
}
class Person {
public void hi() {
System.out.println("你好,我是 Person!");
}
}
public class Main {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in); // 变量名小写
System.out.println("请输入 key(1 或 2):");
String key = scanner.next();
switch (key) {
case "1":
Dog dog = new Dog(); // 首字母大写
dog.cry();
break;
case "2":
Class<?> cls = Class.forName("Person"); // 反射获取类
Object obj = cls.getDeclaredConstructor().newInstance(); // 实例化对象
Method method = cls.getMethod("hi"); // 获取方法
method.invoke(obj); // 调用方法
break;
default:
System.out.println("无效的 key!");
break;
}
}
}
在此之前要知道一个
静态和动态
| 概念 | 静态(Static) | 动态(Dynamic) |
|---|---|---|
| 发生时机 | 编译时(编译器确定) | 运行时(程序运行时才决定) |
| 示例 | String.class、方法重载、变量类型 | str.getClass()、多态调用、反射 |
| 优点 | 快、类型安全 | 灵活、可适应不确定情况 |
| 缺点 | 死板、无法适应运行时变化 | 慢一些,可能不安全(比如反射访问私有字段) |
反射
forName()
可以获取对象
Class.forName(classname) //获取classname类中的所有属性
public class TEST1 {
public String name;
public int age;
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("TEST1");
System.out.println("类名: " + clazz.getName());
}
}
Java的反射API提供了一系列的类和接口来操作Class对象
java.lang.Class:标识嘞的对象,提供了方法来获取类的字段、方法、构造函数等java.lang.reflect.Field:表示类的字段(属性)。提供了访问和修改字段的能力java.lang.reflect.Method:表示类的方法。提供了调用方法的能力java.lang.reflect.Constructor:表示类的构造函数。提供了创建对象的能力
工作流程
- 获取Class对象
- 获取成员信息
- 操作成员
获取对象
通过类字面量
Class<?> clazz = String.class
通过对象实例
String str = "hello";
Class<?> clazz = str.getClass();
通过Class.forName()方法
Class<?> clazz = Class.forName("java.lang.String");
代码示例
public class Java_infect_test1 {
public static void main(String[] args) throws Exception {
Test sun = new Test();
Class<?> clazz = sun.getClass();
System.out.println(clazz); // 输出 class Test
Class<?> clazz2 = Class.forName("Test");
System.out.println(clazz2);
}
}
class Test {
}
对于java.lang.String,可以用Class.forName("java.lang.String")获取这个Class,但是其他两种方法不需要throws Exception,而这种方法由于运行时可能找不到指定的类,所以必须抛出异常
forName有两个函数重载
- Class forName(String name)
- Class forName(String name, boolean initialize, ClassLoader loader)
Class.forName(className)
// 等于
Class.forName(className, true, currentLoader)
默认情况下, forName 的第⼀个参数是类名;第⼆个参数表示是否初始化;第三个参数就 是 ClassLoader
ClassLoader 是什么呢?它就是⼀个“加载器”,告诉Java虚拟机如何加载这个类。关于这个点,后⾯还 有很多有趣的漏洞利⽤⽅法,这⾥先不展开说了。Java默认的 ClassLoader 就是根据类名来加载类, 这个类名是类完整路径,如 java.lang.Runtime
这里去看了狗哥的文章,引用一下狗哥的例子
public class Java05_Reflect_test {
public static void main(String[] args) throws Exception {
new Test1();
}
}
class Test1 {
public Test1() {
System.out.println("我是构造方法");
}
static {
System.out.println("我是静态初始化块");
}
{
System.out.println("我是实例初始块");
}
}
运行结果:
我是静态初始化块
我是实例初始块
我是构造方法
- 静态初始化块(
static {})在类被加载到 JVM 时执行,仅执行一次,只能访问静态成员,同时在任何对象创建之前执行。 - 实例初始化块(
{})每次创建对象时,在构造方法之前执行。 - 构造方法(
public ClassName() {})每次创建对象时,在实例初始化块之后执行
执行顺序如下:
- 类加载,JVM发现
new Test1(),需要先加载类,于是执行静态代码块,只执行一次,类加载时执行 - 创建对象,JVM会创建一个新的实例,会先实现初始化块
- 执行构造函数,最后执行构造器
即静态块 → 实例块 → 构造方法
而forName的这个参数就是控制静态初始化块的,可以看到它的优先级非常高,如果我们可以编写恶意类,就可以把恶意代码放在static{}里,然后用第二个参数直接对这个类进行调用
引用一下代码
public class Java05_Reflect_test {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("Test1");
}
}
class Test1 {
public Test1() {
System.out.println("我是构造方法");
}
static {
System.out.println("我先执行");
}
{
System.out.println("我是实例初始块");
}
}
创建对象
可以利用反射动态创建对象
Class<?> clazz = Class.forName("java.lang.String");
Object obj = clazz.getDeclaredConstructor().newInstance();
dome
import java.lang.reflect.InvocationTargetException;
public class reflect_01 {
public reflect_01() {
System.out.println("我是构造方法");
}
static {
System.out.println("我先执行");
}
{
System.out.println("我是实例初始块");
}
}
class Test01 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<?> clazz = Class.forName("reflect_01");
Object obj = clazz.getDeclaredConstructor().newInstance();
}
}
/*
我先执行
我是实例初始块
我是构造方法
*/
通过反射访问字段
获取类变量
clazz.getField(String name):获取公有字段(包括继承的)
clazz.getDeclaredField(String name):获取本类中声明的字段(包括私有字段)
field.setAccessible(true):允许访问私有字段
field.get(Object obj):获取字段值
field.set(Object obj, Object value):设置字段值
dome
reflect_01.java
public class reflect_01 {
private String name="salyfish";
public String age;
public reflect_01() {
System.out.println("我是构造方法");
}
static {
System.out.println("我先执行");
}
{
System.out.println("我是实例初始块");
}
}
Test01.java
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
class Test01 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
System.out.println("1. 准备加载 reflect_01 类...");
Class<?> clazz = Class.forName("reflect_01");
System.out.println("2. 类加载完成。");
System.out.println("3. 准备创建实例...");
// 这会触发实例初始块,然后触发构造方法
Object obj = clazz.getDeclaredConstructor().newInstance();
System.out.println("4. 实例创建完成。");
Field ageField = clazz.getField("age");
System.out.println("5. 成功获取 public 字段: " + ageField.getName());
ageField.set(obj, "20");
System.out.println("6. 读取 'age' 字段的值: " + ageField.get(obj));
System.out.println("--- 开始获取 private 字段 ---");
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
Object nameValue = nameField.get(obj);
System.out.println("7. 成功获取 'name' 字段的值: " + nameValue);
}
}
/*
1. 准备加载 reflect_01 类...
我先执行
2. 类加载完成。
3. 准备创建实例...
我是实例初始块
我是构造方法
4. 实例创建完成。
5. 成功获取 public 字段: age
6. 读取 'age' 字段的值: 30
--- 开始获取 private 字段 ---
7. 成功获取 'name' 字段的值: salyfish
*/
调用方法
clazz.getMethod(String name, Class… parameterTypes):获取公有方法,包括继承的
clazz.getDeclaredMethod(String name, Class… parameterTypes):获取 本类声明的方法(包括私有方法)
method.setAccessible(true):允许访问私有方法
method.invoke(Object obj, Object… args):调用方法,第一个参数是对象,后面是传入方法的参数
MethodDome
// 文件名: TestDemo.java
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class TestDemo {
// 为了简洁,我们直接 throws Exception
public static void main(String[] args) throws Exception {
// --- 准备工作:获取 Class 对象和实例 ---
System.out.println("1. 加载 MethodDemo 类...");
Class<?> clazz = Class.forName("MethodDemo");
System.out.println("2. 创建 MethodDemo 实例...");
Constructor<?> constructor = clazz.getDeclaredConstructor();
Object obj = constructor.newInstance(); // obj 就是一个 MethodDemo 实例
System.out.println("----------------------------------------");
// --- 场景 1 & 3: 调用 public 方法 (getName) ---
System.out.println("-> 场景 1/3: 调用 public, 无参, 有返回值 的 getName()");
// 1. 获取 Method 对象: getDeclaredMethod(方法名, 参数类型...)
Method getNameMethod = clazz.getDeclaredMethod("getName");
// 2. 调用方法: method.invoke(实例对象, 方法参数...)
// 因为 getName() 没有参数,所以 invoke 只需要传入实例 obj
Object returnValue = getNameMethod.invoke(obj);
System.out.println(" getName() 的返回值: " + returnValue);
System.out.println("----------------------------------------");
// --- 场景 2: 调用 public 有参方法 (setInfo) ---
System.out.println("-> 场景 2: 调用 public, 有参 的 setInfo()");
// 1. 获取 Method 对象,必须指明参数类型
Method setInfoMethod = clazz.getDeclaredMethod("setInfo", String.class, int.class);
// 2. 调用方法,传入实例 obj 和对应的参数
setInfoMethod.invoke(obj, "Salyfish", 99);
// (验证一下) 我们再次调用 getName() 看看 name 是不是被改了
System.out.println(" (验证) 再次调用 getName(): " + getNameMethod.invoke(obj));
System.out.println("----------------------------------------");
// --- 场景 4: 调用 private 私有方法 (getSecret) ---
System.out.println("-> 场景 4: 调用 private 的 getSecret()");
// 1. 获取 private 方法
Method getSecretMethod = clazz.getDeclaredMethod("getSecret", String.class);
// 2. 关键:解除私有方法的访问限制!
getSecretMethod.setAccessible(true);
// 3. 调用,并传入参数 "SECRET:"
Object secretValue = getSecretMethod.invoke(obj, "SECRET:");
System.out.println(" getSecret() 的返回值: " + secretValue);
System.out.println("----------------------------------------");
// --- 场景 5: 调用 public static 静态方法 (staticMethod) ---
System.out.println("-> 场景 5: 调用 static 的 staticMethod()");
// 1. 获取 static 方法
Method staticMethod = clazz.getDeclaredMethod("staticMethod");
// 2. 调用 static 方法
// 关键:静态方法不属于任何实例,所以 invoke 的第一个参数传入 null
staticMethod.invoke(null);
System.out.println("----------------------------------------");
}
}
TestDome
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class TestDome {
public static void main(String[] args) throws Exception {
System.out.println("1. 加载 MethodDemo 类...");
Class<?> clazz = Class.forName("MethodDome");
System.out.println("2. 创建 MethodDemo 实例...");
Constructor<?> constructor = clazz.getDeclaredConstructor();
Object obj = constructor.newInstance();
System.out.println("----------------------------------------");
System.out.println("-> 场景 1/3: 调用 public, 无参, 有返回值 的 getName()");
Method getNameMethod = clazz.getDeclaredMethod("getName");
Object returnValue = getNameMethod.invoke(obj);
System.out.println(" getName() 的返回值: " + returnValue);
System.out.println("----------------------------------------");
System.out.println("-> 场景 2: 调用 public, 有参 的 setInfo()");
Method setInfoMethod = clazz.getDeclaredMethod("setInfo", String.class, int.class);
setInfoMethod.invoke(obj, "Salyfish", 99);
System.out.println(" (验证) 再次调用 getName(): " + getNameMethod.invoke(obj));
System.out.println("----------------------------------------");
System.out.println("-> 场景 4: 调用 private 的 getSecret()");
Method getSecretMethod = clazz.getDeclaredMethod("getSecret", String.class);
getSecretMethod.setAccessible(true);
Object secretValue = getSecretMethod.invoke(obj, "SECRET:");
System.out.println(" getSecret() 的返回值: " + secretValue);
System.out.println("----------------------------------------");
System.out.println("-> 场景 5: 调用 static 的 staticMethod()");
Method staticMethod = clazz.getDeclaredMethod("staticMethod");
staticMethod.invoke(null);
System.out.println("----------------------------------------");
}
}
/*
1. 加载 MethodDemo 类...
2. 创建 MethodDemo 实例...
MethodDemo 的无参构造函数被调用
----------------------------------------
-> 场景 1/3: 调用 public, 无参, 有返回值 的 getName()
...getName() 被调用...
getName() 的返回值: Default User
----------------------------------------
-> 场景 2: 调用 public, 有参 的 setInfo()
...setInfo(String, int) 被调用, 设置为: Salyfish, 99
...getName() 被调用...
(验证) 再次调用 getName(): Salyfish
----------------------------------------
-> 场景 4: 调用 private 的 getSecret()
...private getSecret(String) 被调用...
getSecret() 的返回值: SECRET: Salyfish is 99 years old.
----------------------------------------
-> 场景 5: 调用 static 的 staticMethod()
...public static staticMethod() 被调用...
----------------------------------------
*/