什么是反射

Java 反射是一种机制,允许在运行时检查、获取和操作类、接口、字段、方法等的信息。通过反射,你可以在编译时不知道类的具体信息的情况下,动态地获取并操作类的成员。Java 反射提供了一组类和接口,使得可以在运行时获取类的元数据,调用类的方法,访问类的字段,甚至创建新的类实例。

简单来说,就是在对象生成后,还能通过反射机制来获取对象的信息,操作对象的各种属性,修改属性值,调用方法等

反射机制相关的包

1
java.lang.reflect.*;

反射机制相关类

类名 含义及作用
java.lang.Class 代表整个字节码。代表一个类型,代表整个类。用来获取反射的类
java.lang.reflect.Constructor 代表字节码中的构造方法字节码。用来获取类中的构造方法。
java.lang.reflect.Field 代表字节码中的属性字节码。用来类中的成员变量(静态变量+实例变量),不包括方法。
java.lang.reflect.Method 代表字节码中的方法字节码。代表类中的方法。

获取Class字节码的三种方式

要对一个类进行操作,得先能够获取它的类字节码。

方式 例子
Class.forName(“完整类名带包名”) Class.forname(“com.sec.reflection.Person”)
对象.getClass() person.getClass()
任何类型.class String.class

接着用Person类进行说明

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
package com.security;

public class Person{
// Field
public String name;
private int age;

// Constructor
public Person(){

}
public Person(String name,int age){
this.name = name;
this.age=age;
}

// Method
public void action1(){
System.out.println("action1 with nothing");
}
public void action2(String action){
System.out.println("action2 with "+action);
}
private void action3(String action){
System.out.println("action3 with "+action);
}
@Override
public String toString()
{
return "Person{name="+name+",age="+age+"}";
}
}

实例化反射对象

获取了类的字节码后,对类进行实例化。

1
2
3
4
Person person=new Persion();
Class c=person.getClass();
//调用无参的构造方法,可以直接使用newInstance()
Person p=(Person)c.newInstance();

如果要调用有参数的构造方法,就需要使用到Constructor.

1
2
3
4
5
6
7
//通过getConstructor()方法构造出构造器,构造器的参数为构造函数的参数对应的字节类
//String->String.class
//int->int.class
Constructor constructor=c.getConstructor(String.class,int.class);
//使用newInstance(),参数就和正常的构造函数一样
Person p=(Person)constructor.newInstance("Dragonkeep",22);
System.out.println(p);

执行结果如下:

1
Person{name=Dragonkeep,age=22}

获取反射对象的属性

利用Class类中的getField()方法来获取反射对象中属性的值。getFields()方法会返回所有公共属性的类型的成员变量数组,同样也是Field类。

1
2
Field PersonField=c.getField("name");
System.out.println(PersonField.get(p));

输出结果为Dragonkeep

如果是要获取私用属性的值的话,需要使用getDecleardField()方法。

例如上面例子中的age属性,

1
2
3
Field PersonField =c.getDeclaredField("age");
PersonField.setAccessible(true); //通过setAccessible()方法修改访问属性
System.out.println(PersonField.get(p));

输出结果为22

当然不仅可以get也可以使用set方法

1
2
Field PersonField=c.getDeclaredField("name");
PersonField.set(p,"dragonkeep"); //set(Object obj, Object value)指定对象,和值

获取反射对象的方法

利用Class类中的getMethod(String name,Class<?>... parameterTypes)方法来获取方法.,在配合Method类中的invoke(Object obj, Object... args)进行调用方法。

getMethod:

  • name:为方法名
  • parameterTypes:为方法参数对应的字节码类

invoke:

  • obj:为反射对象
  • args:方法对应的实参

下面举例说明:

1
2
3
4
Method method =c.getMethod("action1");  //调用无参方法
method.invoke(p);
Method method2=c.getMethod("action2",String.class);
method2.invoke(p,"Happy~");

输出结果为:

1
2
action1 with nothing
action2 with Happy~

如果是获取私有属性的方法,就使用getDeclaredMethod.

使用技巧同上getDecleardField()方法

1
2
3
Method method3=c.getDeclaredMethod("action3", String.class);
method3.setAccessible(true);
method3.invoke(p,"Don`t sad");

输出:

1
action3 with Don`t sad

总结:

使用getDecleard()*的方法能访问私有属性和公有,相比get*()方法好用。

Java利用反射执行本地命令代码

反射Runtime类执行本地命令代码,Runtime类位于java.lang.Runtime包中。

1
2
3
4
5
6
7
8
9
10
11
//获取Runtime类对象
Class runtimeClass1=Class.forName("java.lang.Runtime");
//获取构造方法
Constructor constructor=runtimeClass1.getDeclaredConstructor();
constructor.setAccessible(true);
//创造实例
Runtime runtimeObject=(Runtime) constructor.newInstance();
//调用方法
Method methodExec=runtimeClass1.getDeclaredMethod("exec", String.class);
methodExec.setAccessible(true);
methodExec.invoke(runtimeObject,"calc");

java9+后的版本, Java 模块系统不允许通过反射访问 java.lang 包中的私有构造函数

会报java.lang.reflect.InaccessibleObjectException错误。

另外一种命令执行方法

除了Runtime类之外,还有一个类可以用来执行命令ProcessBuilder。同样,我们使用反射来获取构造函数,然后调用start()函数来执行命令。

1
2
3
4
5
6
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();
或者
Class clazz =Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new Object[]{new String[]{"calc.exe"}})).start();
这两种分别对应了ProcessBuilder类的两种构造方法

实测在java17环境下,使用反射调用Runtime类执行命令报错,使用ProcessBuilder成功执行。

image-20240330003311246

参考学习

Java反射+URLDNS链_哔哩哔哩_bilibili

Java反射(超详细!)_一个快乐的野指针~的博客-CSDN博客

本文仅供学习记录,不足之处请指出,谢谢。