Fork me on GitHub

序列化与反序列化

基本概念:

java平台允许我们在内存中创建可复用的Java对象,但只有当JVM处于运行时,这些对象才可能存在。但实际应用中,我们需要JVM停止运行之后任能够保存指定的对象状态,并在将来重新读取被保存的对象。

序列化:是将对象状态转换为可保持或传输的格式的过程

反序列化:将流转换为对象

场景:序列化与反序列化普遍应用在网络传输、RMI等场景中。

package com.example.demo.seria;

import java.io.Serializable;

public class Box implements Serializable {

    /**
     * Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的
     * 在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,
     * 如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException
     */
    private static final long serialVersionUID = 1L;

    private int width;
    private int height;

    /**
     * 材质:
     * transient不能或不应该被序列化
     */
    private transient String texture;

    private static String color = "red";

    public Box(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public String getTexture() {
        return texture;
    }

    public void setTexture(String texture) {
        this.texture = texture;
    }

    @Override
    public String toString() {
        return "Box{" +
                "color=" + color +
                ",width=" + width +
                ", height=" + height +
                ", texture='" + texture + '\'' +
                '}';
    }
}

 

package com.example.demo.seria;


import com.alibaba.fastjson.util.IOUtils;
import org.springframework.core.io.ClassPathResource;

import java.io.*;

public class SerializableUtils {

    private SerializableUtils() {
        //私有构造器,强化不可实列化
        throw new AssertionError();
    }

    public static <T> String getClassName(T obj) {
        Class c = obj.getClass();
        return c.getSimpleName();
    }

    public static File getFile(String filePath) throws IOException {
        File file = new File(filePath);
        return file;
    }

    /**
     * 序列化
     *
     * @param obj
     * @param <T>
     */
    public static <T> void doSerializable(T obj) {

        File file = null;
        FileOutputStream fos = null;
        ObjectOutputStream out = null;
        try {
            file = getFile(  "seria/"+getClassName(obj) + ".out");
            fos = new FileOutputStream(file);
            out = new ObjectOutputStream(fos);
            out.writeObject(obj);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.close(fos);
            IOUtils.close(out);
        }
    }

    /**
     * 反序列化
     *
     * @param filePath
     * @param <T>
     * @return
     */
    public static <T> T doDeserialization(String filePath) {
        File file = new File(filePath);
        FileInputStream fis = null;
        ObjectInputStream in = null;
        T obj = null;
        try {
            fis = new FileInputStream(file);
            in = new ObjectInputStream(fis);

            obj = (T) in.readObject();

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            IOUtils.close(fis);
            IOUtils.close(in);
        }
        return obj;
    }

}

@Test
public void test4(){
//序列化 + transient
Box box = new Box(10,20);
box.setTexture("icon"); //铁
SerializableUtils.doSerializable(box);

Box box1 = new Box(1,2);
box1 = SerializableUtils.doDeserialization("seria/Box.out");
System.out.println(box1);//Box{color=red,width=10, height=20, texture='null'}
}

结果看出,transient变量不被序列化。静态变量呢,如果先序列化,然后将Box类静态变量color改为blue。

运行结果:Box{color=blue,width=10, height=20, texture='null'}

所以结论:transient变量和静态变量不被序列化。

 

现在我们还有一种更为灵活的控制对象序列化和反序列方法,可以在序列化过程中储存其他非this对象包含的数据。

即实现Externalizable接口。

样例:

package com.example.demo.seria;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Date;

public class Ball implements Externalizable {

    private double radius;

    private String clour;

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    public String getClour() {
        return clour;
    }

    public void setClour(String clour) {
        this.clour = clour;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("现在执行序列化方法");
        //可以在序列化时写非自身的变量
        Date d = new Date();
        System.out.println("序列化时间:"+d);
        out.writeObject(d);
        //只序列化 radius,clour 变量
        out.writeObject(radius);
        out.writeObject(clour);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        System.out.println("现在执行反序列化方法");
        Date d=(Date)in.readObject();
        System.out.println("反序列化时间:"+d);
        this.radius= (double) in.readObject();
        this.clour=(String)in.readObject();
    }

    @Override
    public String toString() {
        return "Ball{" +
                "radius=" + radius +
                ", clour='" + clour + '\'' +
                '}';
    }
}
 @Test
    public void test5(){
        //序列化 + transient
        Ball ball = new Ball();

        ball.setRadius(1.0);
        ball.setClour("blue");

        SerializableUtils.doSerializable(ball);
        Ball ball1 = SerializableUtils.doDeserialization("seria/Ball.out");
        System.out.println(ball1);
    }

结果:

现在执行序列化方法
序列化时间:Mon Apr 08 11:59:40 CST 2019
现在执行反序列化方法
反序列化时间:Mon Apr 08 11:59:40 CST 2019
Ball{radius=1.0, clour='blue'}

 谨慎地实现Serializable(74)

 代价1:降低改变被用来序列化类的实现灵活性。

如上面例子,将类Ball.java和Ball.out 放到另一个工程中去反序列化,报错:

目录路径都必须要一致,想想,服务端和客户端交互,两边必须保证实体类路径和实体类一样,不能一方有任何修改。

如果一个服务器对接多个不同的客户端,其中一个客户端有向实体类添加属性的需求,将导致服务端和所有客户端都跟着修改!想想有点小恐怖😖

改成一样的目录路径,结果可以序列化。

PS:有兴趣的可以研究下Hession,服务端客户端交互时,尝试改变实体目录

参考:https://www.cnblogs.com/wqq23/p/4332309.html

代价2:增加了出现Bug和安全漏洞的可能性

为了维护服务端和客户端实体类一致,不可避免需要提供一份包含私有和包级私有的实例域的API违反最低限度地访问域准则;

反序列化机制是一种隐藏的构造器,可以很容易地使对象的约束关系遭到破坏,以及遭受到非法访问。

可通过伪造字节流的方式攻击。

代价3:序列化类变动,所带来的测试负担

原则:被设计用于继承的类,一般不能实现serializable(如果一系列类都被用到某个框架,框架要求所有的参与者都必须实现Serializable除外)

 

posted @ 2019-04-08 15:23  小传风  阅读(257)  评论(0编辑  收藏  举报