Serializable简介
- 其是一个标记接口,其逻辑实现被JDK赋予。Java目前只有四个标记接口:Cloneable、RandomAccess、Serializable、Remote
- 该接口用来标记类的对象是否能够可以进行序列化,或者说串行化。将对象序列化之后,可以进行持久化的储存以及在网络中进行传输。如把对象变成字节流写入到一个文件中,就是一个序列化的过程,实现了对象的持久化储存,然后你的程序可以从这个文件中读取序列化的对象并且把它还原成原来的对象,进行反序列化。如果进行序列化的类的对象没有实现Serializable接口,则会抛出NotSerializableException
- 对象序列化有哪些特点?在对象序列化时,该对象引用的实例变量也会被序列化,如果这个实例变量是一个对象,这个对象也会被序列化。被transient修饰的实例变量也不参与序列化。在对象反序列化时transient修饰的变量将重新初始化,对象初始为null,基本数据类型被初始化为0、false等值。而static修饰的静态变量也不参与序列化的过程,在反序列化时,它的值是运行时虚拟机中该变量的值,并不是对象序列化时”储存”的值。
serialVersionUID是什么,有什么用?在对象序列化时,这个对象都会有一个的serialVersionUID,它是根据类的结构信息计算出来的。在反序列化时,如果对象有了不同的serialVersionUID,则虚拟机将会抛出异常,还原操作将会失败。如在ArrayList类的源码(JDK8)中就可以发现如下属性,所以JDK8中ArrayList类的对象都有一个一致的serialVersionUID值。
简单实例
```java public class SerializableTest { public static void main(String[] args) {
Baby mybaby = new Baby(2, "小官");Mother mother = new Mother(25, mybaby, "官妈");// 此时静态变量name引用的字符串为"小红"mother.setName("小红");ObjectOutputStream oos = null;try (FileOutputStream fos = new FileOutputStream("MyMom.ser");) {// "MyMom.ser"如果不存在会自动创建oos = new ObjectOutputStream(fos);// 将变量引用的对象序列化并写入MyMom.ser这个文件oos.writeObject(mother);} catch (Exception e) {e.printStackTrace();}ObjectInputStream ois = null;Mother reMother = null;// 静态变量name引用的字符串修改为"不是小红"mother.setName("不是小红");try (FileInputStream fis = new FileInputStream("MyMom.ser");) {ois = new ObjectInputStream(fis);reMother = (Mother) ois.readObject();// 将写入MyMom.ser的对象反序列化,由于返回的是Object对象,所以需要进行强转} catch (Exception e) {e.printStackTrace();}// 将反序列化得到的对象reMother打印出来System.out.println(reMother);System.out.println(reMother.name);
} }
class Baby implements Serializable { private int age; private String name;
public Baby(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return new StringJoiner(",").add("age=" + age).add("name=" + name).toString();
}
}
class Mother implements Serializable {
private int i;
// 一个实例变量,也是一个实现了Serializable的类的对象
private Baby mybaby;
public static String name = "LDL";
private transient String nickname;
public Mother(int i, Baby mybaby, String nickname) {
this.i = i;
this.mybaby = mybaby;
this.nickname = nickname;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return new StringJoiner(",").add("i=" + i).add("mybaby=" + mybaby).add("nickname=" + nickname).toString();
}
}
- 可以发现mother对象中,Baby类型的实例变量mybaby所引用的对象也进行了序列化,String类型静态变量name未参与序列化,以及transient修饰的String实例变量nickname也被初始化为null。
<a name="CRrlg"></a>
# 反序列化工具类
```java
public class SerializationUtil {
private static final String PATH = "/Users/icanci/ideaProjects/NewLearn/base-learn/src/main/java/com/ldl/baselearn/_151proposal/serTest.txt";
/**
* 序列化
*
* @param obj obj
* @throws IOException
*/
public static void writeObject(Object obj) throws IOException {
//生成一个文件对象,文件不存在将自动创建文件
File f = new File(PATH);
//构造一个对象输出流oos
ObjectOutputStream oos = null;
//构造一个文件输出流
FileOutputStream fileOutputStream = new FileOutputStream(f);
//构造对象输出流
oos = new ObjectOutputStream(fileOutputStream);
//序列化一个对象到文件变成二进制内容
oos.writeObject(obj);
// 关闭资源
oos.close();
fileOutputStream.close();
}
/**
* 读取序列化的对象
*
* @return 返回序列化的对象
* @throws IOException
*/
public static Object readObject() throws IOException, ClassNotFoundException {
//生成一个文件对象
File f = new File(PATH);
//构建对象输入流对象
ObjectInputStream oos = null;
//构建文件输入流对象
FileInputStream fileOutputStream = new FileInputStream(f);
oos = new ObjectInputStream(fileOutputStream);
Object obj = oos.readObject();
// 关闭资源
oos.close();
fileOutputStream.close();
return obj;
}
}
对象克隆工具类
@UtilityClass
public class CloneUtils {
/**
* 对象拷贝
*
* @param obj 需要拷贝的对象
* @param <T> 泛型
* @return 返回拷贝的对象
*/
@SuppressWarnings("unchecked")
public <T extends Serializable> T clone(T obj) {
T cloneObj = null;
try {
// 有先后顺序的 必须先写,在创建对象的时候没有写,会导致读不出来
ByteArrayOutputStream baos = new ByteArrayOutputStream(); //
ObjectOutputStream oos = new ObjectOutputStream(baos); //
oos.writeObject(obj);
oos.close();
// 分配内存空间,写入原始对象,生成新对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); //
ObjectInputStream ois = new ObjectInputStream(bais);
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
避免用序列化类在构造函数中为不变量赋值
- 先来看一个例子 ```java
@Data class _012ProposalPerson1 implements Serializable { private static final long serialVersionUID = -4648402375052839861L; // 不变量 // 其是归属于对象的 public final String name = “icanci”;
private String password;
}
@Data class _012ProposalPerson2 implements Serializable { private static final long serialVersionUID = 8237020562128022866L; // 不变量 // 其是归属于对象的 public final String name;
//
public _012ProposalPerson2() {
name = "icanci3";
}
}
- 测试类如下
```java
public class _012Proposal {
public static void main(String[] args) throws Exception {
_012ProposalPerson2 person2 = new _012ProposalPerson2();
SerializationUtil.writeObject(person2);
// _012ProposalPerson2 person2 = (_012ProposalPerson2) SerializationUtil.readObject();
// System.out.println(person2);
}
}
- 测试步骤
- Person1 在反序列化的时候,其不变量 name 还是icanci
- Person2 将其序列化,版本定义为V1
- 然后修改 Person2 的name值为 icanci3
- 此时 Person2 的版本是V2 但是 serialVersionUID 没变
- 此时在反序列化,结果是什么? 还是person2 为什么?
- 问题解答
- 序列化文件中存储的是什么?是对象的信息。注意,是对象的信息,而非是类的信息。
- 序列化的实现:JVM从数据流中获取一个Object对象,然后根据数据流中的描述信息查看发现是final变量,于是引用Person2类中的name值,但是JVM发现name竟然没有值,不能引用,所以就不再初始化,保持原值状态,反序列化为啥可以变成对象,因为序列化的就是一个对象,使用的是反射技术,而且使用的构造函数的是Object的构造方法,所以根本没有执行到Person2的构造方法,所以其也就没有执行 参见:ObjectInputStream 源码。
- 序列化源码在:ObjectInputStream#readOrdinaryObject、ObjectInputStream#newInstance ```java private Object readOrdinaryObject(boolean unshared) throws IOException{ //… obj = desc.isInstantiable() ? desc.newInstance() : null; //… return obj; }
Object newInstance() throws InstantiationException, InvocationTargetException, UnsupportedOperationException{ requireInitialized(); if (cons != null) { try { return cons.newInstance();//走到这里 cons是对象obj所属类父类class java.lang.Object的无参构造函数 } catch (IllegalAccessException ex) { // should not occur, as access checks have been suppressed throw new InternalError(ex); } } else { throw new UnsupportedOperationException(); } }
private Object readOrdinaryObject(boolean unshared) throws IOException{ ObjectStreamClass desc = readClassDesc(false);//这里 obj = desc.isInstantiable() ? desc.newInstance() : null; return obj; }
private ObjectStreamClass readClassDesc(boolean unshared) throws IOException { byte tc = bin.peekByte(); ObjectStreamClass descriptor; switch (tc) { case TC_NULL: descriptor = (ObjectStreamClass) readNull(); break; … case TC_CLASSDESC: descriptor = readNonProxyDesc(unshared);// 走到这里 break; default: throw new StreamCorruptedException( String.format(“invalid type code: %02X”, tc)); } if (descriptor != null) { validateDescriptor(descriptor); } return descriptor; }
private ObjectStreamClass readNonProxyDesc(boolean unshared) throws IOException{ desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false)); return desc; }
void initNonProxy(ObjectStreamClass model, Class<?> cl, ClassNotFoundException resolveEx, ObjectStreamClass superDesc) throws InvalidClassException { if (cl != null) { osc = lookup(cl, true); }
static ObjectStreamClass lookup(Class<?> cl, boolean all) { if (entry == null) { entry = new ObjectStreamClass(cl); } }
private ObjectStreamClass(final Class<?> cl) { this.cl = cl; name = cl.getName(); isProxy = Proxy.isProxyClass(cl); isEnum = Enum.class.isAssignableFrom(cl); serializable = Serializable.class.isAssignableFrom(cl); externalizable = Externalizable.class.isAssignableFrom(cl);
Class<?> superCl = cl.getSuperclass();
superDesc = (superCl != null) ? lookup(superCl, false) : null;
localDesc = this;
if (serializable) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (isEnum) {
suid = Long.valueOf(0);
fields = NO_FIELDS;
return null;
}
if (cl.isArray()) {
fields = NO_FIELDS;
return null;
}
suid = getDeclaredSUID(cl);
try {
fields = getSerialFields(cl);
computeFieldOffsets();
} catch (InvalidClassException e) {
serializeEx = deserializeEx =
new ExceptionInfo(e.classname, e.getMessage());
fields = NO_FIELDS;
}
if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
cons = getSerializableConstructor(cl);//这里是重点,真正的获取构造函数的地方
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
}
});
} else {
suid = Long.valueOf(0);
fields = NO_FIELDS;
}
try {
fieldRefl = getReflector(fields, this);
} catch (InvalidClassException ex) {
// field mismatches impossible when matching local fields vs. self
throw new InternalError(ex);
}
if (deserializeEx == null) {
if (isEnum) {
deserializeEx = new ExceptionInfo(name, "enum type");
} else if (cons == null) {
deserializeEx = new ExceptionInfo(name, "no valid constructor");
}
}
for (int i = 0; i < fields.length; i++) {
if (fields[i].getField() == null) {
defaultSerializeEx = new ExceptionInfo(
name, "unmatched serializable field(s) declared");
}
}
initialized = true;
}
//获取构造函数 private static Constructor<?> getSerializableConstructor(Class<?> cl) { Class<?> initCl = cl; while (Serializable.class.isAssignableFrom(initCl)) { if ((initCl = initCl.getSuperclass()) == null) { // 看这里,获取的是基类的构造函数 return null; } } try { Constructor<?> cons = initCl.getDeclaredConstructor((Class<?>[]) null); int mods = cons.getModifiers(); if ((mods & Modifier.PRIVATE) != 0 || ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0 && !packageEquals(cl, initCl))) { return null; } cons = reflFactory.newConstructorForSerialization(cl, cons); cons.setAccessible(true); return cons; } catch (NoSuchMethodException ex) { return null; } } }

<a name="KUsCQ"></a>
# 自定义反序列化内容
- 方案1:Externalizable 是一个手动序列化接口,就是自己来决定序列化的内容
```java
public class SerializableTest2 {
public static void main(String[] args) throws Exception {
Person person = new Person();
person.setUsername("username");
person.setPassword("icanci");
System.out.println(person);
SerializationUtil.writeObject(person);
System.out.println(SerializationUtil.readObject());
}
}
@Data
class Person implements Externalizable {
private String username;
private String password;
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(this.username);
out.writeObject(this.password);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.username = in.readObject().toString();
// this.password = in.readObject().toString();
}
}
- 方案2:重写序列化委托方法
```java
public class _014Proposal {
/**
- 目标是传输的对象不能展示 bonus */ public static void main(String[] args) throws Exception { _014ProposalSalary salary = new _014ProposalSalary(1000, 2500); _014ProposalPerson icanci = new _014ProposalPerson(“icanci”, salary); SerializationUtil.writeObject(icanci); System.out.println(SerializationUtil.readObject()); } }
@Data @AllArgsConstructor @NoArgsConstructor class _014ProposalSalary implements Serializable { private static final long serialVersionUID = 1781672502868195010L;
// 基本工资
private int basePay;
// 绩效
private int bonus;
}
@Data @AllArgsConstructor @NoArgsConstructor class _014ProposalPerson implements Serializable { private static final long serialVersionUID = 2458420478378772673L; private String name; private _014ProposalSalary salary;
/**
* 序列化委托方法
*
* @param out out
* @throws IOException
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(salary.getBasePay());
}
/**
* 反序列化委托方法
*
* @param in in
* @throws IOException
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
salary = new _014ProposalSalary(in.readInt(), 0);
}
}
使用序列化类的私有方法巧妙解决部分属性持久化的问题
- 四个方案
- 为 bonus 加上transient关键字,但是加上transient关键字之后,其在任何外部服务接口,都不能有值,而且,_014ProposalSalary类会是去分布式部署功能,遇到性能瓶颈,再想实现分布式就很难了
- 新增业务对象 换个业务对象丢出去,不放bonus字段
- 请求端过滤,过滤掉bonus的数据,但是不合理,怎么能控制的了呢
- 变更传输契约
- 怎么选择
- 序列化独有的机制:序列化回调。
- 被序列化的类是否有 writeObject 方法,并且检查是否是私有、无返回值的特性。若有,则会委托该方法
- 对对象进行序列化,如果没有,则会使用 ObjectOutputStream 默认的规则进行序列化
- 同样,在恢复序列化对象的时候,也会检查是否有私有的 readObject 方法,如果有,则会通过此方法获取属性
- 关键点
- out.defaultWriteObject() 告知JVM按照默认的规则写入对象,惯例是写在第一句话里面
- in.defaultReadObject() 告知JVM按照默认的规则读入对象,惯例是写在第一句话里面
- out.writeXxx in.readXxx 分别是写入和读出响应的值 类似一个队列,先进先出。
- 如果有复杂逻辑,建议封装对象处理 也是失去了分布式部署能力
-
推荐文章
Java反序列化是否使用默认构造函数初始化对象:https://www.cnblogs.com/kendoziyu/p/how-create-java-bean-when-java-serialization.html
