Java序列化
Java的序列化很简单,只需要实现Serializable接口,然后就可以用ObjectInpuStream、ObjectOutputSteam进行序列化和反序列化了。下面简单演示一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public static void main(String[] args) { User user = new User(); user.setUserName("LTY"); user.setPassword("password"); try(ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("tempFile"))){ outputStream.writeObject(user); System.out.println("Write User "+user.toString()); }catch (Exception e){ e.printStackTrace(); } try(ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("tempFile"))){ User read = (User) inputStream.readObject(); System.out.println("Read User "+read.toString()); }catch (Exception e){ e.printStackTrace(); } }
static class User implements Serializable{ private String userName; private String password;
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
@Override public String toString() { return "User{" + "userName='" + userName + '\'' + ", password='" + password + '\'' + '}'; } }
|
上面就是简单的,序列化和反序列化的过程。默认情况下Java是序列化所有成员变量的,除了静态变量。很好理解,静态变量并不属于某一个具体的对象。在有就是Transient关键字可以控制成员变量的序列化。
1 2 3 4 5 6
| private String userName; private transient String password;
|
刚才的代码,我在password变量前面加上transient关键字,可以看到反序列化出来的结果为空。
Java序列化是将对象转换成的二进制写入文件,并且是可逆的。如果我们在进行Rpc调用的时候,会将这样的二进制格式的数据进行传输。这样是不安全的。对此我们可以自定义序列化和反序列化的一些策略。对一些敏感数据(比如password)进行模糊化。示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| private void writeObject(ObjectOutputStream stream) throws IOException { password = password +"decode"; stream.defaultWriteObject(); stream.writeObject(password); } private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); password = (String) stream.readObject(); password = password.substring(0,password.lastIndexOf("decode")); }
|
上面代码我们在User类中加入了两个方法。这两个方法会在写和读的时候调用,我们可以用这样的方式来自定义序列化和反序列号策略。代码中我们看出,我们在password变量前面加了transient关键字,但是结果仍然反序列化出来了结果。就是因为我自定义了序列化。有兴趣的人可以去看看ArrayList的源码,其中的自定义序列化策略代码很有参考性。所以知识不是死板的我们要知道其中缘由。上面就是我们知道的一些Java序列化的一些用法。知道了怎么用我们在来看看一些实现。为什么我只需要实现了Serializable这个接口就可以序列化以及上面的writeObject和readObject方法是什么时候执行的,代码中我们并没有显示的去调用。那就的看源码了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public final void writeObject(Object obj) throws IOException { if (enableOverride) { writeObjectOverride(obj); return; } try { writeObject0(obj, false); } catch (IOException ex) { if (depth == 0) { writeFatalException(ex); } throw ex; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum<?>) obj, desc, unshared); } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| private void writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared) throws IOException { if (extendedDebugInfo) { debugInfoStack.push( (depth == 1 ? "root " : "") + "object (class \"" + obj.getClass().getName() + "\", " + obj.toString() + ")"); } try { desc.checkSerialize();
bout.writeByte(TC_OBJECT); writeClassDesc(desc, false); handles.assign(unshared ? null : obj); if (desc.isExternalizable() && !desc.isProxy()) { writeExternalData((Externalizable) obj); } else { writeSerialData(obj, desc); } } finally { if (extendedDebugInfo) { debugInfoStack.pop(); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
private Method writeObjectMethod;
private Method readObjectMethod;
void invokeWriteObject(Object obj, ObjectOutputStream out) throws IOException, UnsupportedOperationException { if (writeObjectMethod != null) { try { writeObjectMethod.invoke(obj, new Object[]{ out }); } catch (InvocationTargetException ex) { Throwable th = ex.getTargetException(); if (th instanceof IOException) { throw (IOException) th; } else { throwMiscException(th); } } catch (IllegalAccessException ex) { throw new InternalError(ex); } } else { throw new UnsupportedOperationException(); } }
|
上面我们走了一遍写数据的流程。读数据的流程是一样的这就不说了。在写数据的时候通过instanceof来限制是否实现了Serializable接口,然后通过反射的方式调用了writeObject和readObject方法。
看源码的时候我们发现了一个Externalizable,其实我们实现序列化不止一种方法。Serializable接口只是最方便的,不需要我们写过多的代码。下面我们看看怎么用Externalizable实现序列化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| static class User implements Externalizable{ private String userName; private String password;
public User(){}
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
@Override public String toString() { return "User{" + "userName='" + userName + '\'' + ", password='" + password + '\'' + '}'; }
@Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(userName); out.writeObject(password); }
@Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { userName = (String) in.readObject(); password = (String) in.readObject(); } }
|
我们通过实现Externalizable接口,并且必须复写writeExternal和readExter方法来实现序列化和反序列化。上面我们看源码的时候发现对Serializable和Externalizable做了判断。做不同处理。使用Externalizable意味着我们不能自动的序列化变量,需要将自己需要序列化的变量一个一个的写入到流当中,反序列的化的时候需要一个一个的读取,给成员变量进行初始化。还有使用Externalizable接口必须有有个无惨构造函数。
序列化的一些知识我们回顾的差不多了。然后我们谈谈Rpc中的序列化,在Rpc中我们的接口调用很频繁,要求我们在序列化方面需要很好的性能,如果使用Java原生的这种序列化方式那会很糟糕的,现在一些主流的Rpc框架早已经抛弃了。本人所在的公司用的自己研发的一个Rpc框架,他们没有使用二进制之类的序列化方法,为了从业务的考虑直接用的Json的序列化方法,这样我们可以方便开发人员的调试。后端技术已经很成熟了,比较好的一些序列化技术有Kryo,FST等针对Java语言的,还有一些跨语言的。比如Protostuff,ProtoBuf,Thrift等.当我们累个半死知道了Java的序列化之后,发现主流的一些技术已经把它抛弃了有时候我也不得不感觉郁闷啊。不过学无止境.
人生犹如一本书,愚蠢者草草翻过,聪明人细细阅读。为何如此 . 因为他们只能读它一次。
——保罗