Java安全-反射

p0l1st Lv1

Java安全可以从反序列化说起,而反序列化可以从反射说起。

Java反射机制:对于任意一个类都能知道这个类的所有属性和方法,对于任意一个对象,都能调用它的任意方法。这种动态获取信息以及动态调用方法的功能称为反射机制。

反射的作用:让Java具有动态性

正射与反射

正射

我们需要用到某个类的时候,都会先了解这个类是作什么的,然后实例化这个类,接着用实例化后的对象进行操作,这就是正射。

1
2
Student st= new Student();
st.dohomework();

反射

而反射就是,一开始并不知道我们要初始化的对象是什么,自然也就无法使用new关键字来创建对象了

1
2
Person p = new Person();
Class c = p.getClass();

理解反射的第一步就是要搞清楚Class是什么

Java Class对象

Class类全称java.lang.Class类。

Java是面向对象的语言,讲究万物皆对象,即使再强大的一个类,依然是另一个类(Class类)的对象。也就是说,普通类是Class类的对象,Class类是所有类的类。

程序在运行的时候会编译生成一个.class文件,而这个.class为文件中的内容就是相对应类的所有信息。

比如

1
2
3
4
5
6
7
public class abc {
public static void main(String[] args) {
Test t = new Test();
Class c = t.getClass();
System.out.println(c);
}
}

img

Class类的对象作用是运行时提供或获得某个对象的类型信息。

反射组成相关的类

反射机制相关操作一般位于java.lang.reflect包中

img

重点注意以下类:

  • java.lang.class:类对象
  • java.lang.reflect.Constructor:类的构造器对象
  • java.lang.reflect.Field:类的属性对象
  • java.lang.reflect.Method:类的方法对象

反射常用方法

  • 获取类的方法:forName
  • 实例化类对象的方法:newInstance
  • 获取函数的方法:getMethod
  • 执行函数的方法:invoke

class对象的获取方法

理解Java反射机制首先要理解Class类,而在反射中想要获取一个类或调用一个类的方法,首先需要获取到该类的Class对象

普通类采用以下方法

1
Person person = new Person();

创建Class类时不能使用上述方法,会报错

QQ截图20240712184804

跟进源码看看,发现构造器是私有的,只有JVM才能创建对象,所以无法通过创建对象的方式来获取class对象

QQ截图20240712184908

一般获取class对象有以下三种方法:

  • 实例化对象的getClass方法

存在某个类的实例,通过obj.getClass获取它的类

1
2
Person p = new Person();
Class c = p.getClass();
  • 使用类的.class方法
1
2
Person person = new Person();
Class c = Person.class;
  • Class.forName(String className)动态加载类
1
Class c1 = Class.forName("serialize.Person");

下面来看一个demo

1
2
3
4
5
6
7
8
9
10
11
12
Class c1=Person.class;
System.out.println(c1);
//类.class

Person person = new Person();
Class c2=person.getClass();
System.out.println(c2);
//实例化对象的getClass

Class c3=Class.forName("serialize.Person");
System.out.println(c3);
//Class.forName(String classname)动态加载类

img

上面三种获取Class类的方式中,一般使用第三种通过Class.forName方式动态加载类,这种方式不需要再导入其他类,可以加载任意的类

使用类.class,需要导入类的包,依赖性太强

使用实例化对象的getClass方法,需要本身就创建一个对象,这样就失去了使用反射机制的意义

所以获取class对象,一般使用Class.forName方法获取

获取成员变量Field

获取成员变量Field位于java.lang.reflect.Field包中

  • Field[] getFields():获取所有public修饰的成员变量
  • Field[] getDeclaredFields():获取所有的成员变量,不包括修饰符
  • Field getField(String name):获取指定名称的public修饰的成员变量
  • Field getDeclaredField(String name):获取指定的成员变量

获取成员方法Method

  • Method getMethod(String name,类<?>…parameterTypes):返回该类声明的public方法
  • Method getDeclaredMethod(String name,类<?>…parameterTypes):返回该类声明的所有方法

第一个参数获取该方法的名字,第二个参数获取标识该方法的参数类型

  • Method[] getMethods():获取所有public方法,包括类自身声明的public方法,父类public方法,实现的接口方法
  • Method[] getDeclaredMethods():获取该类中所有方法

Person.java添加以下内容

1
2
3
4
5
6
7
public void study(String s){
System.out.println("学习中..."+s);
}
private void sleep(int age){
System.out.println("睡眠中..."+age);

}

abc.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
package serialize;

import java.lang.reflect.Method;

public class abc {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class c = Class.forName("serialize.Person");
Method[] method1 = c.getMethods();//所有public方法,包括类自身声明的public方法,父类public方法,实现的接口方法
Method[] method2 = c.getDeclaredMethods();//获取该类中的所有方法
System.out.println("以下是该类中的所有public方法");
for (Method m:method1){
System.out.println(m);
}
System.out.println("以下是所有方法");
for (Method m:method2){
System.out.println(m);
}
System.out.println("以下是public的study方法");
Method method3 = c.getMethod("study", String.class);//获取public的study方法
System.out.println(method3);
System.out.println("以下是pricate的sleep方法");
Method method4 = c.getDeclaredMethod("sleep", int.class);//获取pricate的sleep方法
System.out.println(method4);



}
}

img

获取构造函数Constructor

  • Constructor<?>[] getConstructors():只返回public构造函数
  • Constructor<?>[] getDeclaredConstructors():返回所有构造函数
  • Constructor<> getConstructor(类<?>…parameterTypes):匹配和参数配型相符的public构造函数
  • Constructor<> getDeclaredConstructor(类<?>…parameterTypes):匹配和参数配型相符的构造函数

Person.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private String name;
private int age;

//无参构造
public Person() {

}
//构造函数
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//私有构造函数
private Person(String name) {
this.name = name;
}

abc.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
package serialize;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class abc {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class c = Class.forName("serialize.Person");
Constructor[] constructor1= c.getDeclaredConstructors();
Constructor[] constructor2= c.getConstructors();
System.out.println("所有构造函数");
for(Constructor con:constructor1){
System.out.println(con);
}
System.out.println("所有public构造函数");
for (Constructor con:constructor2){
System.out.println(con);
}
System.out.println("public构造函数");
Constructor constructor3 = c.getConstructor(String.class,int.class);
System.out.println(constructor3);
System.out.println("构造函数");
Constructor constructor4 = c.getDeclaredConstructor(String.class);
System.out.println(constructor4);


}
}

img

反射创建类对象

通过反射来生成实例对象,一般使用Class对象的newInstance()方法来创建类对象

1
2
Class c =Class.forName("serialize.Person");
Object o = c.newInstance();

invoke方法,位于java.lang.reflect.Method类中,用于执行某个对象的目标方法,一般和getMethod方法配合使用

1
public Object invoke(Object obj,Object... args)

第一个参数为类的实例,第二个参数为相应函数中的参数

  • obj:从中调用底层方法的对象,必须是实例化对象
  • args:用于方法的调用,是一个object的数组,参数可能是多个

但是第一个参数不固定,如果调用的方法是普通方法,那么第一个参数就是类对象,如果是静态方法,那么第一个参数就是类

写个小demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package serialize;

import java.lang.reflect.Method;

public class abc {
public void reflect(){
System.out.println("reflect");
}
public static void main(String[] args){
try {
Class c = Class.forName("serialize.abc");//创建class对象
Object o = c.newInstance();//创建类实例对象
Method m = c.getMethod("reflect");//获取reflect方法
m.invoke(o);//调用类实例对象方法

}catch (Exception e){
e.printStackTrace();
}

}
}

img

反射构造Runtime类执行

直接使用上面的demo会报错

img

这是因为Runtime类的构造方法是私有的

img

但是可以通过getRuntime来获取Runtime对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package serialize;

import java.lang.reflect.Method;

public class abc {
public void reflect(){
System.out.println("reflect");
}
public static void main(String[] args){
try {
Class c = Class.forName("java.lang.Runtime");
Method m = c.getMethod("exec", String.class);
Method m1 = c.getMethod("getRuntime");
Object o = m1.invoke(c);
m.invoke(o,"calc");

}catch (Exception e){
e.printStackTrace();
}

}
}

img

也可以简化一下

1
2
3
4
5
6
7
8
try{
Class c = Class.forName("java.lang.Runtime");
c.getMethod("exec", String.class).invoke(c.getMethod("getRuntime").invoke(c),"calc");

} catch (Exception e) {
e.printStackTrace();

}

img

小结

说白了反射也就是先获取类,并进行实例化对象,然后获取类里面的属性,调用类里面的方法。

  • Title: Java安全-反射
  • Author: p0l1st
  • Created at : 2024-07-26 14:25:49
  • Updated at : 2024-11-04 10:50:01
  • Link: https://blog.p0l1st.top/2024/07/26/Java安全-反射/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments