[Java安全入门]二.序列化与反序列化

一.概念

Serialization(序列化)是一种将对象以一连串的字节描述的过程;反序列化deserialization是一种将这些字节重建成一个对象的过程。将程序中的对象,放入文件中保存就是序列化,将文件中的字节码重新转成对象就是反序列化

二.要求

只有实现了Serializable或Externalizable接口的类的对象才能被序列化,并且序列化对象的所有属性都需是可序列化的。

三.实现

serializable接口

1.1方法

序列化:创建一个ObjectOutputStream输出流,调用 ObjectOutputStream 对象的 writeObject() 输出可序列化对象

   反序列化:创建一个ObjectInputStream输出流,调用 ObjectInputStream 对象的 readObject()得到反序列化的对象

1.2代码

import java.io.*;
import java.lang.reflect.Method;

class User implements Serializable{
    private String name;
    private int age;
    @Override
    public String toString(){
        return "User{" + "name=" +name + ", age="+age+"}";
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
public class Main {
    public static void main(String[] args) throws Exception {
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("E:\\tao.txt"));
        //创建一个ObjectOutputStream流,将序列化对象输出到tao.txt
        User user=new User();
        user.setName("tao");
        user.setAge(20);
        //实例化User
        out.writeObject(user);
        ObjectInputStream in=new ObjectInputStream(new FileInputStream("E:\\tao.txt"));
        // 创建一个 ObjectOutputStream 输出流
        User tao=(User)in.readObject();
        //将readObject反序列化的结果转化成User类,实例成tao
        System.out.println(tao);
    }
}

User{name=tao, age=20}

1.3注意

①如果实现 Serializable 接口的类有父类,则父类也必须可以序列化,若父类没有实现序列化接口,则父类必须有无参构造函数,否则会抛异常 java.io.InvalidClassException。因为在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。如果没有在父类无参构造函数中对父类变量进行初始化的话,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。

②序列化不保存静态变量,因为序列化保存的是对象的状态而不是类的状态,静态变量是类的状态

③ 使用transient 关键字可以选择不需要序列化的字段

如:

private transient String name;
private transient int age;

进行序列化的时候,name和age都不会被保存

Externalizable接口

2.1注意

①Externalizable接口继承Serializable 接口

②writeExternal()和readExternal()对应writeObject()和readObject()两个方法

③Externalizable序列化没有属性限制,静态变量以及transient 关键字修饰的属性都能被序列化

④必须提供public的无参构造方法,因为在反序列化实现 Externalizabale 接口的类的时需要通过反射创建对象。如果没有无参数的构造方法,在运行时会抛出异常:java.io.InvalidClassException

2.2代码

import java.io.*;
import java.lang.reflect.Method;

class User implements Externalizable{
    private String name;
    private int age;

    public User()
    {
    }//加上public无参构造器
    @Override
    public String toString(){
        return "User{" + "name=" +name + ", age="+age+"}";
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override//重写writeExternal()方法
    public void writeExternal(ObjectOutput out) throws IOException{
        out.writeObject(name);
    }
    @Override//重写wreadExternal()方法
    public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException{
        name=(String)in.readObject();
    }

}
public class Main {
    public static void main(String[] args) throws Exception {
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("E:\\tao.txt"));
        //创建一个ObjectOutputStream流,将序列化对象输出到tao.txt
        User user=new User();
        user.setName("tao");
        user.setAge(20);
        //实例化User
        out.writeObject(user);
        ObjectInputStream in=new ObjectInputStream(new FileInputStream("E:\\tao.txt"));
        // 创建一个 ObjectOutputStream 输出流
        User tao=(User)in.readObject();
        //将readObject反序列化的结果转化成User类,实例成tao
        System.out.println(tao);
    }
}

result

User{name=tao, age=0}

age变成了0

因为使用Externalizable接口,需要重写writeExternal() 与 readExternal() 方法,我只写了name的实现,没有写age,int型默认值为0

四.安全

java反序列化会自动触发readObject()方法,类似于php反序列化的__destruct()函数

java支持自定义writeObject()和readObject()方法

如果某个类中自定义了readObject()方法,当对其的一个实例化对象进行反序列化,就会调用readObject()方法

import java.io.*;
import java.lang.reflect.Method;

class User implements Serializable{
    private String name;
    private int age;
    @Override
    public String toString(){
        return "User{" + "name=" +name + ", age="+age+"}";
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    private void readObject(ObjectInputStream in){
        System.out.println("这是新的readObject!");
    }
}
public class Main {
    public static void main(String[] args) throws Exception {
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("E:\\tao.txt"));
        //创建一个ObjectOutputStream流,将序列化对象输出到tao.txt
        User user=new User();
        user.setName("tao");
        user.setAge(20);
        //实例化User
        out.writeObject(user);
        ObjectInputStream in=new ObjectInputStream(new FileInputStream("E:\\tao.txt"));
        // 创建一个 ObjectOutputStream 输出流
        User tao=(User)in.readObject();
        //将readObject反序列化的结果转化成User类,实例成tao
        System.out.println(tao);
    }
}

结果

这是新的readObject!
User{name=null, age=0}

可见在反序列化的时候实现了新的readObject()

那么就可以命令执行了

 private void readObject(ObjectInputStream in) throws IOException{
        Runtime.getRuntime().exec("calc");
    }

弹计算器了!

这里初步了解java序列化与反序列话,后续构造链会继续学习。

参考博客

java基础知识点2:序列化与反序列化详解_java序列化和反序列化-CSDN博客

javasec/2.java序列化与反序列化.md at master · Maskhe/javasec (github.com)

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tao0845

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值