最近学习xml以及json作为数据源在网络中传输的问题,突然想到java的中自带的序列化和反序列化不正是天然的对象传输方法么,虽然明知道这种方式只在纯java的环境下靠谱,换了其他平台序列化的数据反序列的时候可能会出问题,但是本着学习的态度还是想研究一下。
序列化流中保存了类的表述信息,比如类的名称,类的签名等,反序列化的一方在读取这些信息之后,就需要根据这些信息加载该类,以便通过反射生成对象,并将后续 的对象数据读取进来。
开始的时候考虑的是最简单的情况,也就是将服务器和客户端都在本地。换句话说被序列化的类结构对于反序列化端是透明的。这种情况比较简单,用对象流将对象输出到文件中,然后在反序列端再用对象流输入就行了(由于反序列化端的类加载器可以读到这个类的类结构,所以一切都变得很简单)。
但是这还远远不够,绝大多数情况是客户端对于服务器的类结构一无所知。这时候如果在用上述的方法,由于序列化文件中只有这个对象的信息而没有这个类的信息,导致客户端的类加载器没有办法加载这个类。于是报了经典的CLassNotFound错误。
然后开始在网上找资料,终于这篇文章帮了大忙,问题的解决方法是重写ObjectOutputStream类的annotateClass方法和ObjectInputStream类的resolveClass方法。第一个方法的作用是在序列化对象的时候将这个对象的类信息一起写入序列化话文件。而第二个方法的作用是在反序列化这个对象的时候可以根究文件中的类信息用类加载器先加载这个类,从而完成反序列话的过程。当然还要写一个自己的类加载器,完成上述的从文件读取类信息的过程。
代码:
package com.ren.test;public class MyClassLoader extends ClassLoader { private MyClassLoader() { super(Thread.currentThread().getContextClassLoader()); } public static Class loadClass(String name, byte[] codeSource) { Class c= new MyClassLoader().defineClass(name, codeSource, 0,codeSource.length); System.out.println("load class: " + c.getName() + " over..."); return c; } }
自己的对象输出流
package com.ren.test;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.io.ObjectOutputStream;import java.io.OutputStream;public class MyObjectOutputStream extends ObjectOutputStream{ public MyObjectOutputStream(OutputStream out) throws IOException { super(out); // TODO Auto-generated constructor stub } @Override protected void annotateClass(Class c)throws IOException { if(c == SiraizeObj.class) { //计算类的codeSource文件,这里的计算结果是相对于一个classpath //的相对路径 String relativeFile = c.getName().replace('.', File.separatorChar) + ".class"; //找一个类装载器来获取这个codeSource的输入流 ClassLoader cl = Thread.currentThread().getContextClassLoader(); InputStream is = cl.getResourceAsStream(relativeFile); byte[] bs = new byte[is.available()]; is.read(bs); //将Student.class文件的内容写入到输出流中,这里这个输出流,也是序列化对象时 //底层的输出流 this.writeObject(bs); } } }
自己的输入流
package com.ren.test;import java.io.IOException;import java.io.InputStream;import java.io.ObjectInputStream;import java.io.ObjectStreamClass;public class MyObjectInputStream extends ObjectInputStream { public MyObjectInputStream(InputStream is)throws IOException { super(is); } protected Class resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException { String name = osc.getName(); if(name.startsWith("com.ren.test")) { System.out.println("resolve class: " + name); byte[] codeSource = (byte[])this.readObject(); return MyClassLoader.loadClass(name, codeSource); } else { return super.resolveClass(osc); } }}
测试类
package com.ren.test;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;public class Iotest1 { public static void main(String[] args) { //write_s(); //read_s(); } public static void read_s() { try { FileInputStream fis=new FileInputStream("d:/a.dat"); ObjectInputStream ios=new MyObjectInputStream(fis); Object obj1=ios.readObject(); Class clazz=obj1.getClass(); Field[] fields=clazz.getDeclaredFields(); for(Field field:fields) { System.out.println(field.getName()); field.setAccessible(true); Object obj_test=field.get(obj1); System.out.println(obj_test.toString()); } ios.close(); fis.close(); } catch (Exception e) { e.printStackTrace(); } } public static void write_s() { SiraizeObj obj=new SiraizeObj("ren","123"); try { FileOutputStream fos=new FileOutputStream("d:/a.dat"); ObjectOutputStream oos=new MyObjectOutputStream(fos); oos.writeObject(obj); oos.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }