Java反序列化(一)反射
2024-01-08 00:00:00 # Java

反射在java反序列化中是常用到的一个东西

反射是什么

反射就是java代码在JVM虚拟机中运行时,对于任意一个可以动态获取他的class对象、属性、方法相关的信息

举个栗子

正常情况下,我们在调用某一个类的方法,是先实例化一个类对象,然后对象.方法使用,像这样

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
String name="菜鸡";
public void learn(){
System.out.println("我是"+name);
}
}

public class ReflectionLearn{
public static void main(String[] args) throws Exception {
Person p = new Person();
p.learn();
}
}

再利用反射调用,像这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
String name="菜鸡";
public void learn(){
System.out.println("我是"+name);
}
}

public class ReflectionLearn {
public static void main(String[] args) throws Exception{
Class p = Class.forName("Person");
Person person = (Person) p.newInstance();
person.learn();
}
}

我们可以看到,这个过程是先用forName方法找到了一个名为Person的用,然后用实例化出来一个对象person,再调用learn方法。

反射中常用的方法

forName:获得一个类

getMethod:获得类方法

newInstance:用类实例化出一个对象

invoke:执行函数

这里就要对java中一个特殊的类做一下了解–Class

Class用于表示JVM运行时类或接口的信息,比如获取类名、判断该类是否是一个接口或是普通类等。当一个类被虚拟机加载后会在内存中创建一个该类的Class对象,反射就是在操作这个Class

forName方法

image-20240108013058248

image-20240108013326833

这个方法有两种用法,第一种就是只需要一个类名参数Class.forName("Person")第二种当中是三个参数,第一个还是类名,第二个布尔类型,表示是否初始化,第三个参数表示类加载器,一般情况下第一种用法比较多。

newInstance方法

class.newInstance方法的作用个就是对该类进行实例化

假设现在有一个类如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person {
public String name="李四";
private int age=3;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}


public void learn(){
System.out.println("我是:"+name);
}
public void age(){
System.out.println("我年龄:"+age);
}
}

现在这样去使用

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReflectionLearn {
public static void main(String[] args) throws Exception{
Class<?> p = Class.forName("Person");
//无参构造
Person person = (Person) p.newInstance();
Field[] personfields = p.getDeclaredFields();
for (Field f:personfields){
System.out.println(f);
}
person.learn();
person.age();
}
}

可以看到这里newInstance是没有参数的,并且这个方法是无参的

image-20240108160806574

运行之后我们得到李四3这两个结果,也就是实例化调用的是无参构造方法,那如何调用有参的构造方法呢?就需要用到getConstructor方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ReflectionLearn {
public static void main(String[] args) throws Exception{
Class<?> p = Class.forName("Person");
//无参构造
/*Person person = (Person) p.newInstance();*/
///有参构造
Constructor<?> personconstructor = p.getConstructor(String.class,int.class);
Person person = (Person) personconstructor.newInstance("张三",24);
Field[] personfields = p.getDeclaredFields();
for (Field f:personfields){
System.out.println(f);
}
person.learn();
person.age();
}
}

getField方法

这个方法可以获取Class对象中的成员变量

image-20240108202934325

可以看到有四个相似的方法,带s就是获取多个,带Declared就是不管成员变量是public或是private都可以获取到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ReflectionLearn {
public static void main(String[] args) throws Exception{
Class<?> p = Class.forName("Person");
//无参构造
/*Person person = (Person) p.newInstance();*/
///有参构造
Constructor<?> personconstructor = p.getConstructor(String.class,int.class);
Person person = (Person) personconstructor.newInstance("张三",24);
Field[] personfields = p.getDeclaredFields();
for (Field f:personfields){
System.out.println(f);
}
}
}
1
2
public java.lang.String Person.name
private int Person.age

这里获取到name和age两个变量,然后下一步试图操作他,上一步newInstance("张三",24)实例化对象的时候name的值是张三,下面用set()方法去修改name的值为王五,这里set需要两个参数,第一个是一个对象,第二个顾名思义就是要改变的变量的值

1
2
3
4
5
6
7
8
9
10
11
public void set(Object obj, Object value)
throws IllegalArgumentException, IllegalAccessException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
getFieldAccessor(obj).set(obj, value);
}

需要思考的一个问题就是为什么传person而不是p?首先p是通过Class.forName获得的一个原型类,person是通过p实例化出来的一个对象,我们要修改的是对象的值,所以set()传的参数是person

1
2
Field personname=p.getField("name");
personname.set(person,"王五");

用同样的方法修改age变量的时候,发现报错了,是因为age是一个private类型,需要用getDeclaredField(),然后需要设置setAccessible(true)

image-20240108210253009

1
2
3
4
5
Field personage = p.getDeclaredField("age");
personage.setAccessible(true);
System.out.println(personage);
personage.set(person,666);
person.age();

getMethod方法

这部分跟getField方法用法差不多,主要是调用成员方法的时候用到了invoke

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ReflectionLearn {
public static void main(String[] args) throws Exception{
Class<?> p = Class.forName("Person");
//无参构造
/*Person person = (Person) p.newInstance();*/
///有参构造
Constructor<?> personconstructor = p.getConstructor(String.class,int.class);
Person person = (Person) personconstructor.newInstance("张三",24);
///获取类里面的方法
Method[] personmethods = p.getMethods();
for (Method m : personmethods){
System.out.println(m);
}
///调用类里面的方法
Method personlearn=p.getMethod("learn");
System.out.println(personlearn);
personlearn.invoke(person);
}
}