序文
Java オブジェクト クラスを作成するときに、多くの小さなパートナーがクラスに Serializable を実装すると思います。しかし、それは正確に何をしますか?
シリアライゼーションについて理解する前に、まずシリアライゼーションが使用される理由を理解しましょう。
文章
連載の登場
プロジェクトを行ったことのある人は、市場にマイクロサービス アーキテクチャに基づく多くのプロジェクトがあることを知っています。業務ごとにサービスを分割した後、業務のデカップリングは実現したものの、同時に多くの新たな問題も出てきました。たとえば、データの原子性、サービス間のインターフェイス通信などです。
この時点で、複数の異なるサービスが同じデータ オブジェクトを共有したい場合はどうすればよいでしょうか?
ここでシリアライゼーションの出番です。Java 言語を例にとると、Java がオブジェクトプログラミング言語(人と対話するための言語) に属していることは誰もが知っています。しかし、バイナリの 010101 という数字は機械語に属し、CPU と対話します。次に、Java オブジェクトをマシンに認識させて「バイナリ ストリーム」に変換したい場合、このエンコーディングを変換するプロセスは「シリアライゼーション」と呼ばれます。バイナリ ストリームを Java オブジェクトにデコードするプロセスは、「デシリアライゼーション」と呼ばれます。
ここで重要なのが、異なるサービス間でオブジェクトデータを共有することです。まず、オブジェクトはバイナリ ストリームにシリアル化され、次にネットワークを介して別のサービスに送信されます。次に、そのバイナリ ストリームがデコードされ、再度オブジェクトに変換されます。
追加のポイント: マイクロサービス アーキテクチャに基づくシリアライゼーションでは、通常、JDK によって提供されるシリアライゼーションではなく、Json シリアライゼーションが使用されます。
JDK シリアライゼーション
1. 使用シーン
実際、単一のプロジェクトの場合、シリアル化シナリオを使用できるのは、データベース テーブルに対応するPO エンティティがシリアル化される場合のみです。たとえば、Mybatis フレームワークを使用してオブジェクトをデータベースに挿入する場合です。
また、単純に次のように理解することもできます。オブジェクトをネットワーク経由で送信する必要がある場合にのみ、オブジェクトをシリアル化します。
2.serialVersionUID
Serializable を実装した後、多くの場合、serialVsersionUID 変数を定義します。コードを見て、それが何をするか見てみましょう。
最初に次のエンティティを作成し、シリアル化を実装します。この時点では、serialVsersionUID 変数を定義していません。
/**
* <Description> ******
*
* @author yuSen.zhang
* @version 1.0
* @date 2023/02/27
*/
@Data
public class SerialTest implements Serializable {
private String name;
private Integer age;
private String sex;
@Override
public String toString() {
return "SerialTest{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
public static void serialize() throws IOException {
SerialTest test = new SerialTest();
test.setName("YiSenZ");
test.setAge(11);
test.setSex("男");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(Files.newOutputStream(new File("SerialTest.txt").toPath()));
objectOutputStream.writeObject(test);
objectOutputStream.close();
System.out.println("序列化成功!生内容成文件到:SerialTest.txt");
}
public static void deserialize() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream =
new ObjectInputStream(Files.newInputStream(new File("SerialTest.txt").toPath()));
Object read = objectInputStream.readObject();
SerialTest test = new SerialTest();
BeanUtils.copyProperties(read, test);
objectInputStream.close();
System.out.println("反序列化结果为:" + test);
}
@SneakyThrows
public static void main(String[] args) {
//执行序列化
serialize();
//执行反序列化
deserialize();
}
}
SerialTest.txt を見てみましょう (その内容、エンコード形式の問題は無視してください)、コンソールに出力された結果を見てみましょう。
SerialTest.txt:
sr )com.epidemic.report.controller.SerialTest$taget java/lang/Integer;
L namet Ljava/lang/String;
L sexq ~ xpsr java.lang.Integer⠤÷8 I valuexr java.lang.Number˂
xp t YiSenZt 男
控制台:
序列化成功!生内容成文件到:SerialTest.txt
反序列化结果为:SerialTest(name=YiSenZ, age=11, sex=男)
この時点では、シリアライゼーションとデシリアライゼーションの両方が正常であることがわかります。
現時点で、エンティティにフィールドを追加してから逆シリアル化すると、問題は発生しますか?
//新增一个测试用的字段
private String addField;
@SneakyThrows
public static void main(String[] args) {
//直接执行反序列化
deserialize();
}
Exception in thread "main" java.io.InvalidClassException: com.epidemic.report.controller.SerialTest; local class incompatible: stream classdesc serialVersionUID = 2594804517751749556, local class serialVersionUID = 2578299843277314095
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2028)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1875)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2209)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1692)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:508)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:466)
at com.epidemic.report.controller.SerialTest.deserialize(SerialTest.java:48)
at com.epidemic.report.controller.SerialTest.main(SerialTest.java:60)
この時点で以前にシリアル化されたファイルを逆シリアル化すると、シリアル番号が異なることがわかります.これは、Serializable を実装すると、serialVsersionUID が自動的に追加されるためです. したがって、次のように結論付けることができます。
1.serialVsersionUIDオブジェクトのシリアル化の前後で同じです。
2. serialVsersionUID が定義されていない場合、コンパイラは自動的に宣言します。
もちろん、serialVsersionUID をロックとして理解することもできます。オブジェクトはドアです。外出するとき (シリアル化)、ドア (オブジェクト) をロックします。もちろん、ドアを開けるために戻ってきたときに、対応するキーを使用する必要があります (デシリアライゼーション)。
注: static によって変更されたフィールドはシリアル化されません。シリアル化は、クラスの状態ではなく、オブジェクトの状態を保存するためです。したがって、静的は無視されます。
3.JDKシリアライゼーションの欠陥
上記の実際の操作では、シリアル化とはオブジェクトの状態をバイナリ ストリームに変換して操作することであることがわかります。このストリームが盗まれて変更された場合はどうなりますか? はい、これはセキュリティ上の問題につながります。これは Java でも公式に説明されています。信頼されていないデータの逆シリアル化は本質的に危険であり、回避する必要があります。
また、現在 Java シリアライゼーションはJava ベースの実装フレームワークにのみ適用可能であり、他の言語が Java シリアライゼーション プロトコルを統合しない場合、それらは相互に通信できません。
さらに、シリアル化のパフォーマンスは比較的低く、ネットワーク通信のプロセスでは、パフォーマンスが重要な指標になることがよくあります。詳細については、次の ByteBuffer と JDK のシリアル化効率の比較を参照してください。
@Data
@ToString
public class SerialTest implements Serializable {
private String name;
private Integer age;
private String sex;
@SneakyThrows
public static void jdkSerializeTest() {
SerialTest test = new SerialTest();
test.setName("ObjectOutputStream");
test.setAge(18);
test.setSex("男");
long startTime = System.currentTimeMillis();
for (int i = 0; i < 500; i++) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(os);
outputStream.writeObject(test);
outputStream.flush();
outputStream.close();
byte[] bytes = os.toByteArray();
os.close();
}
long endTime = System.currentTimeMillis();
System.out.println("ObjectOutputStream序列化时间:" + (endTime - startTime));
}
@SneakyThrows
public static void byteBufferTest() {
SerialTest test = new SerialTest();
test.setName("ByteBuffer");
test.setSex("男");
long startTime = System.currentTimeMillis();
for (int i = 0; i < 500; i++) {
ByteBuffer byteBuffer = ByteBuffer.allocate(2048);
byte[] uName = test.getName().getBytes(StandardCharsets.UTF_8);
byte[] uSex = test.getSex().getBytes(StandardCharsets.UTF_8);
byteBuffer.putInt(uName.length);
byteBuffer.put(uName);
byteBuffer.putInt(uSex.length);
byteBuffer.put(uSex);
byte[] bytes = new byte[byteBuffer.remaining()];
}
long endTime = System.currentTimeMillis();
System.out.println("ByteBuffer序列化时间:" + (endTime - startTime));
}
@SneakyThrows
public static void main(String[] args) {
//JDK序列化
jdkSerializeTest();
//ByteBuffer序列化
byteBufferTest();
}
}
ObjectOutputStream序列化时间:52
ByteBuffer序列化时间:5