能否将一个对象持久化,下次启动的时候能够获得之前保存的状态,而不是需要对象从零开始?能否在网络上直接传送一个对象,接收方在接收对象后直接使用?Java为这种场景提供了原生的支持——序列化。通过序列化,对象可以被编译成字节码储存在文件中,同时也可以从文件中还原出一个对象。
Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程。
通过对象输出流java.io.ObjectOutputStream可以将一个对象序列化,即编译成字节码输出去;
通过对象出入流java.io.ObjectInputStream可以对象的字节码流反序列化成对象。
注意异常抛出和捕获。
1.实现Serializable接口,该接口提供wirteObject(ObjectOutputStream)方法实现对象序列化,该接口提供readObject(ObjectInputStream)方法实现对象反序列化。
序列化
package cc.appweb.www;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class RedisTest {
public static void main(String[] args){
try{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Eclipse\\workspace\\Test\\test.txt"));
SerializableTest test = new SerializableTest(1, "123", 123.01, '3');
oos.writeObject(test);
oos.flush();
oos.close();
// ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Eclipse\\workspace\\Test\\test.txt"));
// SerializableTest test = (SerializableTest) ois.readObject();
// test.printData();
//
System.out.println("Done!");
}catch(Exception e){
e.printStackTrace();
}
}
}
class SerializableTest implements Serializable{
private int number;
private String str;
private double dou;
private char ch;
public SerializableTest(int n, String s, double d, char c){
number = n;
str = s;
dou = d;
ch = c;
}
public void printData(){
System.out.println("Integer: "+number);
System.out.println("String: "+str);
System.out.println("Double: "+dou);
System.out.println("Character: "+ch);
}
}
可以看到,目标目录下test.txt保存了字节码数据。
将注释转过来,实现反序列。
package cc.appweb.www;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class RedisTest {
public static void main(String[] args){
try{
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Eclipse\\workspace\\Test\\test.txt"));
// SerializableTest test = new SerializableTest(1, "123", 123.01, '3');
// oos.writeObject(test);
// oos.flush();
// oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Eclipse\\workspace\\Test\\test.txt"));
SerializableTest test = (SerializableTest) ois.readObject();
test.printData();
ois.close();
System.out.println("Done!");
}catch(Exception e){
e.printStackTrace();
}
}
}
输出
Integer: 1
String: 123
Double: 123.01
Character: 3
Done!
writeObject和readObject方法为JVM会在序列化和反序列化java对象时会分别调用的两个方法,修饰符都是private。
添加了writeObject()私有方法,将数值number向左移动一位。
class SerializableTest implements Serializable{
private int number;
private String str;
private double dou;
private char ch;
public SerializableTest(int n, String s, double d, char c){
number = n;
str = s;
dou = d;
ch = c;
}
private void writeObject(ObjectOutputStream oos) throws Exception{
number <<= 1;
oos.defaultWriteObject();
}
public void printData(){
System.out.println("Integer: "+number);
System.out.println("String: "+str);
System.out.println("Double: "+dou);
System.out.println("Character: "+ch);
}
}
明显字节码已经不一样
Integer: 2
String: 123
Double: 123.01
Character: 3
Done!
3.实现Externalizable接口,实现其中的writeExternal(ObjectOutput)和readExternal(ObjectInput)方法,自己去实现该对象的序列与反序列。
package cc.appweb.www;
import java.io.DataInput;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.Externalizable;
public class RedisTest {
public static void main(String[] args){
try{
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Eclipse\\workspace\\Test\\test.txt"));
// SerializableTest test = new SerializableTest(1, "123", 123.01, '3');
// oos.writeObject(test);
// oos.flush();
// oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Eclipse\\workspace\\Test\\test.txt"));
SerializableTest test = (SerializableTest) ois.readObject();
test.printData();
ois.close();
System.out.println("Done!");
}catch(Exception e){
e.printStackTrace();
}
}
}
class SerializableTest implements Externalizable{
private int number;
private String str;
private double dou;
private char ch;
public SerializableTest(){
}
public SerializableTest(int n, String s, double d, char c){
number = n;
str = s;
dou = d;
ch = c;
}
public void writeExternal(ObjectOutput oo) throws java.io.IOException{
oo.writeObject(str);
oo.writeChar(ch);
}
public void readExternal(ObjectInput oi) throws java.io.IOException, java.lang.ClassNotFoundException{
str = (String) oi.readObject();
ch = oi.readChar();
}
public void printData(){
System.out.println("Integer: "+number);
System.out.println("String: "+str);
System.out.println("Double: "+dou);
System.out.println("Character: "+ch);
}
}
Integer: 0
String: 123
Double: 0.0
Character: 3
Done!
以下参考:关于 Java 对象序列化您不知道的 5 件事
1.序列化允许重构
可以表述为:
Java Object Serialization 规范可以自动管理的关键任务:
将新字段添加到类中
将字段从 static 改为非 static
将字段从 transient 改为非 transient
具体操作如下:
编写一个简单的类实现Serializable,将其序列化到文件中
package cc.appweb.www;
import java.io.Serializable;
public class SerializableTest implements Serializable{
private int number;
private String str;
private double dou;
private char ch;
public SerializableTest(){
}
public SerializableTest(int n, String s, double d, char c){
number = n;
str = s;
dou = d;
ch = c;
}
public void printData(){
System.out.println("Integer: "+number);
System.out.println("String: "+str);
System.out.println("Double: "+dou);
System.out.println("Character: "+ch);
}
}
package cc.appweb.www;
import java.io.DataInput;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.Externalizable;
public class RedisTest {
public static void main(String[] args){
try{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Eclipse\\workspace\\Test\\test.txt"));
SerializableTest test = new SerializableTest(1, "123", 123.01, '3');
oos.writeObject(test);
oos.flush();
oos.close();
// ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Eclipse\\workspace\\Test\\test.txt"));
// SerializableTest test = (SerializableTest) ois.readObject();
// test.printData();
// ois.close();
System.out.println("Done!");
}catch(Exception e){
e.printStackTrace();
}
}
}
调用jdk的serialver程序获取该序列化的serialVersionUID
此时需要改变SerializableTest类(添加字段、删除字段),将该字段添加进新的类里可以将该序列化重构出来。
package cc.appweb.www;
import java.io.Serializable;
public class SerializableTest implements Serializable{
private static final long serialVersionUID = -8634238426192165505L;
private int number;
private String str;
private double dou;
private char ch;
private long l;
public SerializableTest(){
}
public SerializableTest(int n, String s, double d, char c){
number = n;
str = s;
dou = d;
ch = c;
}
public void printData(){
System.out.println("Integer: "+number);
System.out.println("String: "+str);
System.out.println("Double: "+dou);
System.out.println("Character: "+ch);
System.out.println("Long: "+l);
}
}
package cc.appweb.www;
import java.io.DataInput;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.Externalizable;
public class RedisTest {
public static void main(String[] args){
try{
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Eclipse\\workspace\\Test\\test.txt"));
// SerializableTest test = new SerializableTest(1, "123", 123.01, '3');
// oos.writeObject(test);
// oos.flush();
// oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Eclipse\\workspace\\Test\\test.txt"));
SerializableTest test = (SerializableTest) ois.readObject();
test.printData();
ois.close();
System.out.println("Done!");
}catch(Exception e){
e.printStackTrace();
}
}
}
新添加的字段将被置为默认值
Integer: 1
String: 123
Double: 123.01
Character: 3
Long: 0
Done!
由于一个对象的私有信息都储存在字节码流中,所以说对象的信息是很不安全的。如上面所说,可以为一个序列化的类添加writeObect()、readObject(),所以可以做到一些在序列序列化前加密与在反序列化解密的工作。
package cc.appweb.www;
import java.io.Serializable;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
public class SerializableTest implements Serializable{
private int number;
public SerializableTest(){
}
public SerializableTest(int num){
number = num;
}
private void writeObject(ObjectOutputStream oos) throws Exception{
number = number ^ 0xaaaaaaaa;
oos.defaultWriteObject();
}
private void readObject(ObjectInputStream ois) throws Exception{
ois.defaultReadObject();
number = number ^ 0xaaaaaaaa;
}
public void printData(){
System.out.println("Number:" + number);
}
}
package cc.appweb.www;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class RedisTest {
public static void main(String[] args){
try{
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Eclipse\\workspace\\Test\\test.txt"));
// SerializableTest test = new SerializableTest(123456789);
// oos.writeObject(test);
// oos.flush();
// oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Eclipse\\workspace\\Test\\test.txt"));
SerializableTest test = (SerializableTest) ois.readObject();
test.printData();
ois.close();
System.out.println("Done!");
}catch(Exception e){
e.printStackTrace();
}
}
}
Number:123456789
Done!
jdk提供了更高安全性的可序列化对象供程序在序列化与反序列化是进行加密和签名的操作。当然原则上,通过使用 writeObject 和 readObject 也可以实现加密和签名。
如果需要对整个对象进行加密和签名,可以将它放在一个 javax.crypto.SealedObject 或 java.security.SignedObject 包装器中。两者都实现了序列化的,所以将对象包装在 SealedObject 中可以围绕原对象创建一种 “包装盒”。必须有对称密钥才能解密,而且密钥必须单独管理。同样,也可以将 SignedObject 用于数据验证,并且对称密钥也必须单独管理。结合使用这两种对象,便可以轻松地对序列化数据进行密封和签名,而不必强调关于数字签名验证或加密的细节。
4.序列化替换
private Object writeReplace(); // 置换对象,造成实际执行序列化的非this指向的对象
private Object readResolve(); // 置换反序列化出来的对象
通过这一层关系可以对对象进行代理序列化
实体
package cc.appweb.www;
import java.io.Serializable;
public class SerializableInstance implements Serializable {
private int number;
private String str;
public SerializableInstance(int n, String s) {
number = n;
str = s;
}
public int getInt(){
return number;
}
public String getString(){
return str;
}
}
package cc.appweb.www;
import java.io.Serializable;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
public class SerializableProxy implements Serializable{
private String str;
private int n;
public SerializableProxy(SerializableInstance instance) {
str = instance.getString();
n = instance.getInt();
}
private Object writeReplace() throws Exception{
Integer i = new Integer(n);
return i; // 真正被序列化的为String s
}
}
序列化和反序列化
package cc.appweb.www;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import javax.crypto.SealedObject;
import javax.crypto.Cipher;
public class RedisTest {
public static void main(String[] args){
try{
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Eclipse\\workspace\\Test\\test.txt"));
// SerializableProxy test = new SerializableProxy(new SerializableInstance(123, "456789"));
// oos.writeObject(test);
// oos.flush();
// oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Eclipse\\workspace\\Test\\test.txt"));
Integer i = (Integer) ois.readObject();
System.out.println(i.intValue());
ois.close();
System.out.println("Done!");
}catch(Exception e){
e.printStackTrace();
}
}
}
输出
123
Done!
认为序列化流中的数据总是与最初写到流中的数据一致,这没有问题。但是,正如一位美国前总统所说的,“信任,但要验证”。对于序列化的对象,这意味着验证字段,以确保在反序列化之后它们仍具有正确的值,“以防万一”。为此,可以实现 ObjectInputValidation接口,并覆盖 validateObject() 方法。如果调用该方法时发现某处有错误,则抛出一个 InvalidObjectException。