日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

你不知道的java对象序列化的秘密

發布時間:2024/2/28 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 你不知道的java对象序列化的秘密 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 簡介
  • 什么是序列化
  • 重構序列化對象
  • 序列化不是加密
  • 使用真正的加密
  • 使用代理
  • Serializable和Externalizable的區別
  • 總結

簡介

你知道序列化可以使用代理嗎?你知道序列化的安全性嗎?每個java程序員都聽說過序列化,要存儲對象需要序列化,要在網絡上傳輸對象要序列化,看起來很簡單的序列化其實里面還隱藏著很多小秘密,今天本文將會為大家一一揭秘。

更多精彩內容且看:

  • 區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新
  • Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
  • Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新
  • java程序員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程

更多內容請訪問www.flydean.com

什么是序列化

序列化就是將java對象按照一定的順序組織起來,用于在網絡上傳輸或者寫入存儲中。而反序列化就是從網絡中或者存儲中讀取存儲的對象,將其轉換成為真正的java對象。

所以序列化的目的就是為了傳輸對象,對于一些復雜的對象,我們可以使用第三方的優秀框架,比如Thrift,Protocol Buffer等,使用起來非常的方便。

JDK本身也提供了序列化的功能。要讓一個對象可序列化,則可以實現java.io.Serializable接口。

java.io.Serializable是從JDK1.1開始就有的接口,它實際上是一個marker interface,因為java.io.Serializable并沒有需要實現的接口。繼承java.io.Serializable就表明這個class對象是可以被序列化的。

@Data @AllArgsConstructor public class CustUser implements java.io.Serializable{private static final long serialVersionUID = -178469307574906636L;private String name;private String address; }

上面我們定義了一個CustUser可序列化對象。這個對象有兩個屬性:name和address。

接下看下怎么序列化和反序列化:

public void testCusUser() throws IOException, ClassNotFoundException {CustUser custUserA=new CustUser("jack","www.flydean.com");CustUser custUserB=new CustUser("mark","www.flydean.com");try(FileOutputStream fileOutputStream = new FileOutputStream("target/custUser.ser")){ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);objectOutputStream.writeObject(custUserA);objectOutputStream.writeObject(custUserB);}try(FileInputStream fileInputStream = new FileInputStream("target/custUser.ser")){ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);CustUser custUser1 = (CustUser) objectInputStream.readObject();CustUser custUser2 = (CustUser) objectInputStream.readObject();log.info("{}",custUser1);log.info("{}",custUser2);}}

上面的例子中,我們實例化了兩個CustUser對象,并使用objectOutputStream將對象寫入文件中,最后使用ObjectInputStream從文件中讀取對象。

上面是最基本的使用。需要注意的是CustUser class中有一個serialVersionUID字段。

serialVersionUID是序列化對象的唯一標記,如果class中定義的serialVersionUID和序列化存儲中的serialVersionUID一致,則表明這兩個對象是一個對象,我們可以將存儲的對象反序列化。

如果我們沒有顯示的定義serialVersionUID,則JVM會自動根據class中的字段,方法等信息生成。很多時候我在看代碼的時候,發現很多人都將serialVersionUID設置為1L,這樣做是不對的,因為他們沒有理解serialVersionUID的真正含義。

重構序列化對象

假如我們有一個序列化的對象正在使用了,但是突然我們發現這個對象好像少了一個字段,要把他加上去,可不可以加呢?加上去之后原序列化過的對象能不能轉換成這個新的對象呢?

答案是肯定的,前提是兩個版本的serialVersionUID必須一樣。新加的字段在反序列化之后是空值。

序列化不是加密

有很多同學在使用序列化的過程中可能會這樣想,序列化已經將對象變成了二進制文件,是不是說該對象已經被加密了呢?

這其實是序列化的一個誤區,序列化并不是加密,因為即使你序列化了,還是能從序列化之后的數據中知道你的類的結構。比如在RMI遠程調用的環境中,即使是class中的private字段也是可以從stream流中解析出來的。

如果我們想在序列化的時候對某些字段進行加密操作該怎么辦呢?

這時候可以考慮在序列化對象中添加writeObject和readObject方法:

private String name;private String address;private int age;private void writeObject(ObjectOutputStream stream)throws IOException{//給age加密age = age + 2;log.info("age is {}", age);stream.defaultWriteObject();}private void readObject(ObjectInputStream stream)throws IOException, ClassNotFoundException{stream.defaultReadObject();log.info("age is {}", age);//給age解密age = age - 2;}

上面的例子中,我們為CustUser添加了一個age對象,并在writeObject中對age進行了加密(加2),在readObject中對age進行了解密(減2)。

注意,writeObject和readObject都是private void的方法。他們的調用是通過反射來實現的。

使用真正的加密

上面的例子, 我們只是對age字段進行了加密,如果我們想對整個對象進行加密有沒有什么好的處理辦法呢?

JDK為我們提供了javax.crypto.SealedObject 和java.security.SignedObject來作為對序列化對象的封裝。從而將整個序列化對象進行了加密。

還是舉個例子:

public void testCusUserSealed() throws IOException, ClassNotFoundException, NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException {CustUser custUserA=new CustUser("jack","www.flydean.com");Cipher enCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");Cipher deCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");SecretKey secretKey = new SecretKeySpec("saltkey111111111".getBytes(), "AES");IvParameterSpec iv = new IvParameterSpec("vectorKey1111111".getBytes());enCipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);deCipher.init(Cipher.DECRYPT_MODE,secretKey,iv);SealedObject sealedObject= new SealedObject(custUserA, enCipher);try(FileOutputStream fileOutputStream = new FileOutputStream("target/custUser.ser")){ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);objectOutputStream.writeObject(sealedObject);}try(FileInputStream fileInputStream = new FileInputStream("target/custUser.ser")){ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);SealedObject custUser1 = (SealedObject) objectInputStream.readObject();CustUser custUserV2= (CustUser) custUser1.getObject(deCipher);log.info("{}",custUserV2);}}

上面的例子中,我們構建了一個SealedObject對象和相應的加密解密算法。

SealedObject就像是一個代理,我們寫入和讀取的都是這個代理的加密對象。從而保證了在數據傳輸過程中的安全性。

使用代理

上面的SealedObject實際上就是一種代理,考慮這樣一種情況,如果class中的字段比較多,而這些字段都可以從其中的某一個字段中自動生成,那么我們其實并不需要序列化所有的字段,我們只把那一個字段序列化就可以了,其他的字段可以從該字段衍生得到。

在這個案例中,我們就需要用到序列化對象的代理功能。

首先,序列化對象需要實現writeReplace方法,表示替換成真正想要寫入的對象:

public class CustUserV3 implements java.io.Serializable{private String name;private String address;private Object writeReplace()throws java.io.ObjectStreamException{log.info("writeReplace {}",this);return new CustUserV3Proxy(this);} }

然后在Proxy對象中,需要實現readResolve方法,用于從系列化過的數據中重構序列化對象。如下所示:

public class CustUserV3Proxy implements java.io.Serializable{private String data;public CustUserV3Proxy(CustUserV3 custUserV3){data =custUserV3.getName()+ "," + custUserV3.getAddress();}private Object readResolve()throws java.io.ObjectStreamException{String[] pieces = data.split(",");CustUserV3 result = new CustUserV3(pieces[0], pieces[1]);log.info("readResolve {}",result);return result;} }

我們看下怎么使用:

public void testCusUserV3() throws IOException, ClassNotFoundException {CustUserV3 custUserA=new CustUserV3("jack","www.flydean.com");try(FileOutputStream fileOutputStream = new FileOutputStream("target/custUser.ser")){ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);objectOutputStream.writeObject(custUserA);}try(FileInputStream fileInputStream = new FileInputStream("target/custUser.ser")){ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);CustUserV3 custUser1 = (CustUserV3) objectInputStream.readObject();log.info("{}",custUser1);}}

注意,我們寫入和讀出的都是CustUserV3對象。

Serializable和Externalizable的區別

最后我們講下Externalizable和Serializable的區別。Externalizable繼承自Serializable,它需要實現兩個方法:

void writeExternal(ObjectOutput out) throws IOException;void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

什么時候需要用到writeExternal和readExternal呢?

使用Serializable,Java會自動為類的對象和字段進行對象序列化,可能會占用更多空間。而Externalizable則完全需要我們自己來控制如何寫/讀,比較麻煩,但是如果考慮性能的話,則可以使用Externalizable。

另外Serializable進行反序列化不需要執行構造函數。而Externalizable需要執行構造函數構造出對象,然后調用readExternal方法來填充對象。所以Externalizable的對象需要一個無參的構造函數。

總結

本文詳細分析了序列化對象在多種情況下的使用,并講解了Serializable和Externalizable的區別,希望大家能夠喜歡。

本文作者:flydean程序那些事

本文鏈接:http://www.flydean.com/java-serialization/

本文來源:flydean的博客

歡迎關注我的公眾號:程序那些事,更多精彩等著您!

總結

以上是生活随笔為你收集整理的你不知道的java对象序列化的秘密的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。