Java反射机制学习
2022-03-17 23:19:35 # Java安全

反射

Reflection(反射) 是 Java 程序开发语言的特征之一,反射允许运行中的Java程序在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分,甚至能直接操作程序的私有属性。

获取class类对象

我们正常类加载的方式是:

  • 导入包名—>通过new实例化—>取得实例化对象

而反射可以利用下面三种方法获取字节码对象:

1
2
3
4
Class.forName("类的全路径");
类名.class
对象.getClass();
classLoader.loadClass("类的全路径");

实现反射相关API:

1
2
3
4
java.lang.Class           代表一个类
java.lang.reflect.Method 代表类的方法
java.lang.reflect.Field 代表类的成员属性
java.lang.reflect.Constructor 代表类的构造方法

举个例子

Person.java

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
45
public class Person {
public String name;
private int age;

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
private String show(String name) {
//System.out.println(name+"");
return name+"正在洗澡";
}
private static void teststatic(){
System.out.println("static method start");
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

}

User.java

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
public class User extends Person{
private int id;
private String username;
private String password;
public int age;

public User(){}
public User(int id, String username, String password, int age) {
this.id = id;
this.username = username;
this.password = password;
this.age = age;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}
private void show(String username,String password) {
System.out.println("用户名:"+username+",密码:"+password);
}

public void study(String username) {
System.out.println(username+"正在学习~");
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
}

获取class类对象的四种方法

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 Test {
public static void main(String[] args) {
//1.获取class对象
User user1 = new User();
Class c1 = user1.getClass();
System.out.println("第一种"+c1);

//2.通过类的方式获取
Class c2 = User.class;
System.out.println("第二种"+c2);

//3.class.forName() 将字节码文件加载到内存
Class c3;
try {
c3 = Class.forName("User");
System.out.println("第三种"+c3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//4. classLoader 类加载器
ClassLoader classLoader = Test.class.getClassLoader();
Class aClass = null;
try {
aClass = classLoader.loadClass("User");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.print("第四种"+aClass);
}
}

image-20220318000221843

获取成员属性

1
2
3
4
getFields()          获取所有public修饰的成员属性,包括父类
getDeclaredFields() 获得当前类的所有属性,包括private
getField(变量名)
getDeclaredField(变量名)

获取成员属性的四个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void geFieldTest() throws ClassNotFoundException, NoSuchFieldException {
System.out.println("getDeclaredFields方法");
Class c = Class.forName("User");
Field[] fields = c.getDeclaredFields();
for (Field f : fields) {
System.out.println(f);
}
System.out.println("getFields方法");
Field[] fields1 = c.getFields();
for (Field field : fields1) {
System.out.println(field);
}
System.out.println("getField方法");
Field name = c.getField("name");
System.out.println( name.toString());
System.out.println("getDeclaredField方法");
Field id2 = c.getDeclaredField("id");
System.out.println( id2.toString());
}

image-20220318003645675

获取构造方法

1
2
3
4
5
获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)//获得当前类指定的构造方法

获取构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void getConstructorTest() throws ClassNotFoundException, NoSuchMethodException {
Class clazz = Class.forName("User");
System.out.println("getDeclaredConstructors方法:");
Constructor[] cs = clazz.getDeclaredConstructors();
for (Constructor con : cs) {
System.out.println(con);
}
System.out.println("getConstructors方法:");
Constructor[] cs1 = clazz.getConstructors();
for (Constructor con : cs1) {
System.out.println(con);
}
System.out.println("getConstructor方法:");
Constructor constructor = clazz.getConstructor();
System.out.println(constructor);
System.out.println("getDeclaredConstructor方法:");
Constructor declaredConstructor1 = clazz.getDeclaredConstructor(int.class, String.class, String.class, int.class);
System.out.println(declaredConstructor1);
}

image-20220318010136369

获取父类的构造方法:

1
2
3
4
5
6
7
8
public void getFatherConstructorTest() throws ClassNotFoundException {
Class clazz = Class.forName("User");
Class superclass = clazz.getSuperclass();
Constructor[] cs = superclass.getDeclaredConstructors();
for (Constructor con : cs) {
System.out.println(con);
}
}

image-20220318010301538

获取成员方法

1
2
3
4
getMethods()//获取当前类和父类的所有公有的public方法,并以数组返回
getMethod(方法名,参数类型列表)//获取某个public方法
getDeclaredMethods()//只查询当前类的所有定义的方法(包含private),并以数组返回
getDeclaredMethod()//获取某个方法,传入的第一个参数为方法名,第二个参数为方法参数

获取成员方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void getMethod() throws ClassNotFoundException, NoSuchMethodException {
Class c = Class.forName("User");
System.out.println("getDeclaredMethods方法:");
Method[] method = c.getDeclaredMethods();
for (Method meth : method) {
System.out.println(meth);
}
System.out.println("getDeclaredMethod方法:");
Method show = c.getDeclaredMethod("show", String.class,String.class);
System.out.println(show);
System.out.println("getMethods方法:");
Method[] method1 = c.getMethods();
for (Method meth : method1) {
System.out.println(meth);
}
System.out.println("getMethod方法:");
Method study = c.getMethod("study", String.class);
System.out.println(study);
}

image-20220318011322727

反射调用属性

调用属性:

1
2
3
4
5
6
7
8
9
public void getFieldValue() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class c = Class.forName("User");
Field agefield = c.getField("age");
User u = new User(1,"jack","123456",18);
System.out.println(agefield.getInt(u));
Field usernamefield = c.getDeclaredField("username");
usernamefield.setAccessible(true);
System.out.println(usernamefield.get(u));
}

image-20220318013317502

反射调用方法

调用方法:

1
2
3
4
5
6
7
8
9
public void getMethodValue() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class c = Class.forName("User");
Method study = c.getDeclaredMethod("study", String.class);
User u = new User(1,"jack","123456",18);
study.invoke(u, "jack");
Method show = c.getDeclaredMethod("show", String.class,String.class);
show.setAccessible(true);
show.invoke(u,"test","123");
}

想要访问show()方法,使用getDeclaredMethod()方法,并传入方法名,以及形参。又因为该方法为private,所以需要使用setAccessible()设置访问权限。

注:setAccessible作用是启动和禁止访问安全检查的开关,参数为true表示反射的对象在使用时应该取消java语言访问检查,参数为false则表示反射的对象实施对java语言的访问检查。

image-20220318013911374

反射创建对象

上面的例子我们是用new的方式来创建的对象,也可以通过反射创建对象

1
2
Class.newInstance()   只能够调用无参的构造方法,即默认的构造方法;要求构造方法必须是public类型的。
Constructor.newInstance() 可以根据传入的参数,调用任意的构造方法; 特定情况下可以调用私有的构造方法。

创建对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void getInstance() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1. 获得class对象
Class clazz = Class.forName("User");
// 2. 获取无参构造器
Constructor declaredConstructor = clazz.getDeclaredConstructor();
// 3. Constructor.newInstance() 创建对象
Object test = declaredConstructor.newInstance();
User user = (User) test;
// 4. 获取方法
Method study = clazz.getDeclaredMethod("study", String.class);
study.invoke(user, "jack");
Object o = clazz.newInstance();
User user1 = (User) o;
Method show = clazz.getDeclaredMethod("show", String.class, String.class);
show.setAccessible(true);
show.invoke(user, "jack","123");
}

image-20220318014627850

因为User类有两个构造方法,我们尝试用另一个有参构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
public void getInstance1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1. 获得class对象
Class clazz = Class.forName("User");
// 2. 获取有参构造器
Constructor declaredConstructor2 = clazz.getDeclaredConstructor(int.class,String.class,String.class,int.class);
// 3. Constructor.newInstance() 创建对象
Object test1 = declaredConstructor2.newInstance(1,"jack","123",18);
User user2= (User) test1;
System.out.println(user2.toString());
// 4. 获取方法
Method study1 = clazz.getDeclaredMethod("study", String.class);
study1.invoke(user2,"jack");
}

image-20220318015105785

结论为:在newInstance创建对象的时候,对应有参构造器,传入形参。

再尝试反射创建对象并调用public show() 私有方法

1
2
3
4
5
6
7
8
		Class clazz = Class.forName("User");
Constructor declaredConstructor2 = clazz.getDeclaredConstructor(int.class,String.class,String.class,int.class);
Object test1 = declaredConstructor2.newInstance(1,"jack","123",18);
User user2= (User) test1;
System.out.println(user2.toString());
Method show1 = clazz.getDeclaredMethod("show", String.class,String.class);
show1.setAccessible(true);
show1.invoke(user2,"jack","123456");

image-20220318015907570

应用

反射调用命令执行常用到的Runtime类

正常使用Runtime类执行系统命令为

1
2
3
4
Process p = Runtime.getRuntime().exec("calc");
//使用ProcessBuilder类进行命令执行
new ProcessBuilder(new String[]{"calc.exe"}).start();

反射的过程首先获得Runtime类,再查看该类的构造方法

1
2
3
4
5
Class clazz = Class.forName("java.lang.Runtime");
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for(Constructor con : declaredConstructors){
System.out.print(con);
}

image-20220318020639913

只有一个私有的无参构造器,获取该构造器并通过setAccessible设置访问权限。

1
2
Constructor declaredConstructor = clazz.getDeclaredConstructor();
declaredConstructor.setAccessible(true);

有了构造器,就可以通过构造器创建对象

1
Object o = declaredConstructor.newInstance();

查看当前类的所有方法

1
2
3
4
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method meth : declaredMethods){
System.out.println(meth);
}

image-20220318021137716

可以看到exec()方法对应的都是public属性,且可以传入String类型,也可以传入String[]类型,比如使用传入String类型的方法,那在getDeclaredMethod()方法后传入的第一个参数为exec()方法名,第二个参数为String.class。

1
Method method = clazz.getDeclaredMethod("exec", String.class);

并使用invoke()方法执行exec()方法

1
method.invoke(o,"calc.exe");

所以综上,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void getRuntimeExec() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 1. 获得class对象
Class clazz = Class.forName("java.lang.Runtime");
// 2. 获取无参构造器
Constructor declaredConstructor = clazz.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
// 3. Constructor.newInstance() 创建对象
Object o = declaredConstructor.newInstance();
//Runtime o1 = (Runtime) o;
// 4. 获取方法
Method show = clazz.getDeclaredMethod("exec", String.class);
show.invoke(o,"calc.exe");
}

image-20220318021718437

也可以调用传入String[] 参数类型的方法

1
2
3
4
5
6
7
8
9
public void getRuntimeExecs() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class clazz = Class.forName("java.lang.Runtime");
Constructor declaredConstructor = clazz.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance();
String[] cmd = new String[]{"cmd.exe", "-c", "calc.exe"};
Method method = clazz.getDeclaredMethod("exec", String[].class);
method.invoke(o, cmd);
}

image-20220318022232597

同样,ProcessImpl类、ProcessBuilder类也可以反射调用执行系统命令

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
 //ProcessBuilder类->获取传入List.class的构造  
public void getProcessBuilder1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
Class clazz = Class.forName("java.lang.ProcessBuilder");
Constructor declaredConstructor = clazz.getDeclaredConstructor(List.class);
ArrayList<Object> lists = new ArrayList<>();
lists.add("calc.exe");
Object o = declaredConstructor.newInstance((List) lists);
Method start = clazz.getDeclaredMethod("start");
start.invoke(o);
}

//ProcessBuilder类->获取传入String[].class的构造
public void getProcessBuilder2() throws ClassNotFoundException, IOException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
new ProcessBuilder().command("calc.exe").start();
Class clazz = Class.forName("java.lang.ProcessBuilder");
String[] cmds = new String[]{"calc.exe"};
Constructor declaredConstructors = clazz.getDeclaredConstructor(String[].class);
Object o = declaredConstructors.newInstance((Object) cmds);
Method command = clazz.getDeclaredMethod("start");
command.invoke(o);
}

//ProcessImpl类
public void getProcessImpl1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clazz = Class.forName("java.lang.ProcessImpl");
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
String[] cmds = new String[]{"calc.exe"};
Method startMethod = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
startMethod.setAccessible(true);
startMethod.invoke(null, cmds, null, null,null, false);//这里需要的五个参数,第一个参数为null,因为调用的方法是这个类的静态方法
}

方法清单

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
获取包名 类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名

获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)

获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)

获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)

反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance(666,”海绵宝宝”);//执行含参构造创建对象
clazz.getConstructor(int.class,String.class)//获取构造方法

反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null

反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法

参考