美文网首页
[Java基础]关于Java序列化从0到1

[Java基础]关于Java序列化从0到1

作者: zazalu | 来源:发表于2016-09-14 19:45 被阅读0次

序列化最基本使用


public class A implements Serializable{
    private static final long serialVersionUID = 9175036933185692367L;
    private final String name;
    private String Id;
    public A(String name,String Id){
        this.name = name;
        this.Id = Id;
    }    public void methodA(){
        System.out.println("My name is zazalu" + Id);
    }
}

class B{
    public static void main(String[] args) throws IOException {
        //将A序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("test.txt")));
        A a = new A("zazaluA","123456");
        oos.writeObject(a);
    }
}

class C{
    //将A反序列化
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("test.txt")));
        A a = (A) ois.readObject();
        a.methodA();    //输出My name is zazalu123456
    }
}

以上就是最基本的序列化使用方式,不多说。接下来来思考Serializable到底在底层发生了什么

其实,上面的代码其实隐式调用了2个方法

private void writeObject(java.io.ObjectOutputStream out) throws IOException;
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;

接下来我们来演示下,将这两个方法在A类中重写

private void readObject(ObjectInputStream in){
    System.out.println("调用了readObject");
}
private void writeObject(java.io.ObjectOutputStream out){
    System.out.println("调用了writeObject");
}

然后我们在此运行下,控制台输出就变成了这样:


Paste_Image.png Paste_Image.png

可见,这两个方法都被调用了。

那么接下来我们就要学习这两个方法到底可以做什么?

首先我们先了解下下面两个方法

in.defaultReadObject(); 
out.defaultWriteObject(); //in和out是对象输入输出流的两个实例

这两个方法其实是对象流读写对象的默认的底层调用方法。
当我们使用out.writeObject()和in.readObject()的时候,就会默认去调用

in.defaultReadObject(); 
out.defaultWriteObject(); 

这两个方法是会把允许序列化的属性和方法进行序列化和反序列化(被transient关键字修饰的属性方法是不会被默认序列化的)


既然

in.defaultReadObject(); 
out.defaultWriteObject(); 

这两个是默认的方法,那么看来我们可以自定义序列化过程
如何自定义序列化,其实方法有很多
主要是使用

private void readObject(ObjectInputStream in){
    System.out.println("只反序列化Id属性");
    out.writeObject(Id);
}
private void writeObject(java.io.ObjectOutputStream out){
    System.out.println("只序列化Id属性");
    this.Id = in.readObject();
}

在序列化的类中重写这两个方法进行自定义序列化!上述写法就只序列化了一个Id属性,而name属性没有序列化!

但是我们这样自定义序列化是很粗糙的,因为我们没有处理很多细节方法的问题,比如NotActiveException我们就没有做处理。所以我们最好是

in.defaultReadObject(); 
out.defaultWriteObject(); 

让这两个方法被调用。所以我们可以如此实现
1.先将所有属性方法哟哦那个transient关键字修饰
2.将你想要序列化的字段用out.writeObject()和in.readObject()来实现
3.在最后调用in.defaultReadObject(); 和in.defaultReadObject(); 来保证细节处理正常

private void readObject(ObjectInputStream in){
    System.out.println("只反序列化Id属性");
    out.writeObject(Id);
    in.defaultReadObject();  
}
private void writeObject(java.io.ObjectOutputStream out){
    System.out.println("只序列化Id属性");
    this.Id = in.readObject();
    in.defaultReadObject(); 
}


序列化安全问题


1.首先,序列化出来的字节流,可以进行分析,从而给攻击者暴露了所有的信息,解决方法:在序列化时对字段进行加密,反序列化的时候再进行解密
2.其次,攻击者可以伪造字节流,然后通过反序列化获取珍贵的信息。
比如(Period是一个类,有start和end这两个Date属性)

ByteArrayOutputStream bos = new ByteArrayOutputStream();  
ObjectOutputStream oos = new ObjectOutputStream(/*new FileOutputStream(new File("D:/obj.data"))*/bos);  
oos.writeObject(new Period(new Date(), new Date()));  
/*
伪造一个字节流,这个字节流以一个有效的Period实例所产生的字节流作为开始,然后附加上两个额外的引用,指向Period实例中的两个内部私有Date域,攻击者通过引用攻击内部域
*/          
byte[] ref = {0x71, 0 , 0x7e, 0, 5};  
bos.write(ref);  
ref[4] = 4;  
bos.write(ref);  
          
ObjectInputStream ois = new ObjectInputStream(/*new FileInputStream(new File("D:/obj.data"))*/new ByteArrayInputStream(bos.toByteArray()));  
period = (Period)ois.readObject();  
start = (Date)ois.readObject();  //获取到了Period里的start属性引用
end = (Date)ois.readObject();  //获取到了Period里的end属性引用

......
//获取到的end属性,我们可以修改,因为是对象类型,所以period里的end属性也发生了变化!这实在是太恶毒了
end.setYear(78); 

解决办法:使用保护性拷贝!

private void readObject(java.io.ObjectInputStream in)

 throws IOException, ClassNotFoundException{

 in.defaultReadObject();

 start = new Date(start.getTime());

 end = new Date(end.getTime()); //重新创建start和end对象引用

 }

可是这办法因为二次修改了属性的引用,所以无法和final字段配合使用,更多时候我们会希望属性是final的,这样就不会被轻易修改了。

加强解决办法:使用序列化代理

序列化代理


http://blog.csdn.net/drifterj/article/details/7802586
参看这个bolg,写的非常具体了!

相关文章

  • [Java基础]关于Java序列化从0到1

    序列化最基本使用 以上就是最基本的序列化使用方式,不多说。接下来来思考Serializable到底在底层发生了什么...

  • 序列化

    序列化方式 1、Java序列化技术 1.1基础概念 Java 序列化是指把 Java 对象转换为字节序列的过程;(...

  • Java-序列化-反序列化

    Thanks Java基础学习总结——Java对象的序列化和反序列化java序列化反序列化原理Java 序列化的高...

  • 0基础学Java?我从彷徨到曙光的心路历程

    0基础学Java?我从彷徨到曙光的心路历程 我是从0基础开始学Java的,在来到尚学堂之前,我对java一无所知。...

  • Java序列化

    Java序列化的几种方式以及序列化的作用 Java基础学习总结——Java对象的序列化和反序列化

  • 在Alibaba广受喜爱的“Java突击宝典”简直太牛了

    0-1年:夯实基础 1.Java基础(Java异常分类及处理+Java反射+Java注解+Java内部类+Java...

  • 总结

    java基础 Java中多态的理解 反射 Java序列化与反序列化 Volatile和Synchronized e...

  • Spark序列化

    Java序列化 有关Java对象的序列化和反序列化也算是Java基础的一部分,首先对Java序列化的机制和原理进行...

  • 文章总目录

    @TOC 文章总目录 ✂ 【java】 ✳ java基础 java基础--(0)--常用命令 java基础--(0...

  • 反序列化漏洞

    【1】Java反序列化漏洞从入门到深入https://xz.aliyun.com/t/2041 【2】Java饭序...

网友评论

      本文标题:[Java基础]关于Java序列化从0到1

      本文链接:https://www.haomeiwen.com/subject/wigvettx.html