序列化与反序列化
基本概念:
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除外)