反射和反序列化

前提代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Person {
public String name;
private int age;

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

public void Say(){
System.out.println(this.name+": My name is " + name + " and I am " + age + " years old.");
}

private void Say(String message ){
System.out.println(this.name+": "+message);
}
}

反射

直接创建类的方法

1
2
3
Person person = new Person(1);
person.setName("Jane");
person.Say();

反射创建

使用的场景:

在程序运行的时候动态的加载类

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
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Main {
public static void main(String[] args) throws Exception {
// 找到这个类
Class c = Class.forName("Person");
// 创建一个类的实例话对象
// 1. 通过c.newInstance()
// Object o1=c.newInstance();
// 2. 通过c.getConstructor()
Constructor cs=c.getDeclaredConstructor(String.class,int.class);
Object o2=cs.newInstance("张三",20);

// 函数调用
// 1. public 函数的调用
Method publicMethod=c.getMethod("Say");
publicMethod.invoke(o2);
// 2. private 函数的调用
Method privateMethod=c.getDeclaredMethod("Say",String.class);
privateMethod.setAccessible(true);// 必须要有这个
privateMethod.invoke(o2,"Hello");

// 成员变量
// 1. 获取所有的public 成员变量
for (Field field:c.getFields()){
System.out.println("public 成员变量:"+field.getName());
}
// 2. 获取所有的成员变量
for (Field field:c.getDeclaredFields()){
System.out.println("成员变量:"+field.getName());
}
// 3. 修改public 成员变量的值
Field publicField=c.getField("name");
publicField.set(o2,"李四");
publicMethod.invoke(o2);
// 4. 修改private 成员变量的值
Field privateField=c.getDeclaredField("age");
privateField.setAccessible(true);
privateField.set(o2,30);
publicMethod.invoke(o2);

}
}

当需要调用private 的函数或者是需要修改private 的变量的时候

需要使用含有declared 的函数

还需要设置对应的setAccessible 为true

1
2
Field privateField=c.getDeclaredField("age");
privateField.setAccessible(true);

反序列化

为什么需要序列化

由于代码都是运行在内存,当发生了特殊情况的时候,内存中的数据就没有了
如果内存中存放的是用户的session 类的话

当发生了意外那么所有的session 信息都没有
这个时候就可以存储一些数据到数据库或者是文件中

总的来说,序列化的作用就是为了存放对象的数据,或者是用于传输

一个简单的序列化demo

将一个对象保存成文件

Main.java

1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static void main(String[] args) throws Exception {
SerializationClass serializationClass=new SerializationClass("张三",20);
Serialization.SerializationToFile("test.bin",serializationClass);
Object o=Serialization.UnserializationFromFile("test.bin");
SerializationClass s=(SerializationClass) o;
s.name="李四";
s.Say();
}
}

SerializationClass.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
import java.io.Serializable;

public class SerializationClass implements Serializable
{
public String name;
private int age;

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

public void Say(){
System.out.println(this.name+": My name is " + name + " and I am " + age + " years old.");
}

private void Say(String message ){
System.out.println(this.name+": "+message);
}

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

简介

在Java中实现对象反序列化非常简单,实现java.io.Serializable(内部序列化)java.io.Externalizable(外部序列化)接口即可被序列化,其中java.io.Externalizable接口只是实现了java.io.Serializable接口。

反序列化类对象时有如下限制:

  1. 被反序列化的类必须存在。

  2. serialVersionUID值必须一致。

    在Java编程中,serialVersionUID是一个用于序列化的类的版本号。它作为Serializable接口的一部分,确保了序列化和反序列化过程中类的版本一致性。当一个类对象需要通过网络发送或在文件系统中存储时,Java序列化机制通过比较serialVersionUID来确认发送和接收的是同一版本的类对象。

并且反序列化不会直接调用构造函数,反序列化创建对象的时候使用的是sun.reflect.ReflectionFactory.newConstructorForSerialization 创建了一个反序列化专用的Constructor(反射构造方法对象) 使用这个特殊的Constructor可以绕过构造方法创建类实例(前面章节讲sun.misc.Unsafe 的时候我们提到了使用allocateInstance方法也可以实现绕过构造方法创建类实例)。

自定义序列化和反序列化

实现了java.io.Serializable接口的类,还可以定义如下方法(反序列化魔术方法),这些方法将会在类序列化或反序列化过程中调用:

  1. private void writeObject(ObjectOutputStream oos),自定义序列化。
  2. private void readObject(ObjectInputStream ois),自定义反序列化。
  3. private void readObjectNoData()
  4. protected Object writeReplace(),写入时替换对象。
  5. protected Object readResolve()

当我们对DeserializationTest类进行序列化操作时,会自动调用(反射调用)该类的writeObject(ObjectOutputStream oos)方法,对其进行反序列化操作时也会自动调用该类的readObject(ObjectInputStream)方法,也就是说我们可以通过在待序列化或反序列化的类中定义readObjectwriteObject方法,来实现自定义的序列化和反序列化操作,当然前提是,被序列化的类必须有此方法,并且方法的修饰符必须是private

类加载

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
package org.example;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
public static void main(String[] args) throws IOException {
String classPath = "/Users/august/code/java/JavaSecLearn/src/main/java/fun/au9u5t/User.class";
File file = new File(classPath);
byte[] classData = new byte[(int) file.length()];
try {
java.io.FileInputStream fileInputStream = new java.io.FileInputStream(file);
fileInputStream.read(classData);
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
MyClassLoader myClassLoader = new MyClassLoader();
myClassLoader.setClassData(classData);
try {
Class userClass = myClassLoader.loadClass("fun.au9u5t.User");
// 使用 Constructor.newInstance() 方法
Constructor<?> constructor = userClass.getConstructor(String.class, int.class);
Object user = constructor.newInstance("hhhhh", 20);
System.out.println(user);
Method method = userClass.getMethod("Say");
method.invoke(user);

} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
System.out.println("fun.au9u5t.User 加载成功");

}
}

反射和反序列化
https://tsy244.github.io/2025/04/20/java安全/反射和反序列化/
Author
August Rosenberg
Posted on
April 20, 2025
Licensed under