当前位置:Java -> EclipseStore 高性能序列化器
自从我20多年前学习了Java之后,我就一直想要一个简单的解决方案来序列化Java对象图,但是又不会带来Java所带来的序列化安全性和性能问题。它应该可以像下面这样实现...
byte[] data = serializer.serialize(objectGraph);
Node objectGraphDeserialized = serializer.deserialize(data);
您想知道如何通过新的开源项目EclipseSerializer来实现这一点吗?您来对了地方。
在我们看Open-Source项目EclipseStore Serializer之前,我想简要回顾一下来自Java序列化本身的挑战。这将是背景信息,以便了解项目的强大之处。
Java序列化是Java编程语言提供的一种机制,允许您将对象的状态转换为字节流。这个字节流可以很容易地存储在文件中、通过网络发送或以其他方式持久化。稍后,您可以反序列化字节流,重新构造原始对象,有效地保存和还原对象的状态。
以下是关于Java序列化的一些关键点:
Serializable接口: 要使Java类可序列化,它需要实现Serializable
接口。这个接口没有任何方法;它充当标记接口,指示该类的对象可以被序列化。
import java.io.Serializable;
public class MyClass implements Serializable {
// class members and methods
}
序列化过程: 要对对象进行序列化,通常使用ObjectOutputStream
。您创建此类的实例,并将您的对象写入其中。例如:
try (FileOutputStream fileOut = new FileOutputStream("object.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
MyClass obj = new MyClass();
out.writeObject(obj);
} catch (IOException e) {
e.printStackTrace();
}
反序列化过程: 要对对象进行反序列化,您使用ObjectInputStream
。您从文件或网络中读取字节流,然后使用ObjectInputStream
重新创建对象。
try (FileInputStream fileIn = new FileInputStream("object.ser");
ObjectInputStream in = new ObjectInputStream(fileIn)) {
MyClass obj = (MyClass) in.readObject();
// Now 'obj' contains the deserialized object
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
版本考虑: 如果更改了一个序列化类的结构(例如添加或删除字段或更改其类型),则可能会导致旧的序列化对象的反序列化失败。Java提供了诸如serialVersionUID之类的机制来帮助处理版本和兼容性问题。
安全考虑: 序列化可能存在安全风险,特别是如果从不受信任的来源反序列化数据。在反序列化过程中可能会执行恶意代码。为了减轻这一风险,您应该仔细验证和清理您反序列化的任何数据,或者考虑使用JSON或XML等替代序列化机制。
自定义序列化: 在您的类中,您可以通过提供writeObject
和readObject
方法来自定义序列化和反序列化过程。这些方法允许您控制对象状态如何写入和从字节流中读取。
总之,Java序列化是一个用于持久化对象并在网络上发送它们的宝贵功能。但是,它也带来了一些与版本和安全性相关的挑战,所以在处理不受信任的数据源时应谨慎使用。
Java序列化可能会引入一些安全问题,特别是当从不受信任的来源反序列化数据时。以下是与Java序列化相关的一些安全问题:
要减轻这些安全问题,请考虑以下最佳实践:
serialVersionUID
管理不同版本的序列化对象之间的版本和兼容性。总之,Java序列化可能会引入严重的安全风险,尤其是处理不受信任的数据时。非常重要的是采取预防措施、验证输入,并考虑使用替代序列化方法或库来增强安全性。此外,保持Java运行环境的最新状态至关重要,因为较新的Java版本可能包含安全改进和已知漏洞的修复。
许多论文和讲座建议通过使用XML或JSON来规避序列化的安全风险。这是要传输的数据的结构化表示。也存在安全问题,但我将在另一篇文章中讨论这些问题。然而,需要解决的是两个问题。首先,数据必须被转换为文本表示。这通常需要比纯二进制模型更多的数据量。此外,像图像的二进制数据这样的数据必须被记录,以便只能传输可打印或UTF-8字符。这个过程需要大量时间,而且当将其转换为XML并从XML转换回原始格式时通常需要大量的内存。
在大多数情况下导致问题的第二点是数据结构。在XML和JSON中,对象引用只能以更可管理的方式存储。这使得处理变得更加复杂,慢速和资源密集。尽管可以使用许多可靠的解决方案将Java对象转换为XML或JSON,我建议偶尔寻找新的方法。
现在,让我们在这篇文章中来谈谈实际的东西。首先需要依赖。为此,我们在pom.xml中添加以下指令。第一个版本是在写这篇文章时准备的,而且有一个SNAPSHOT版本(1.0.0-SNAPSHOT)可以从存储库中获得。在这种情况下,您仍然必须使用SNAPSHOT 存储库。
在pom.xml内部定义。
<dependency>
<groupId>org.eclipse.serializer</groupId>
<artifactId>serializer</artifactId>
<version>{maven-version-number}</version>
</dependency>
一旦准备好并获取了依赖,剩下的事情会很快就进行。首先测试,我们创建了一个名为Node的类。每个Node可以有一个右孩子和一个左孩子。有了这样,我们就可以创建一棵树。
private String id;
private Node leftNode;
private Node rightNode;
例如,我创建了以下结构,然后使用序列化器对其进行了一次序列化和反序列化。
Node rootNode = new Node("rootNode");
Node leftChildLev01 = new Node("Root-L");
Node rightChildLev01 = new Node("Root-R");
leftChildLev01.addLeft(new Node("Root-L-R"));
leftChildLev01.addLeft(new Node("Root-L-L"));
rightChildLev01.addLeft(new Node("Root-R-L"));
rightChildLev01.addRight(new Node("Root-R-R"));
rootNode.addLeft(leftChildLev01);
rootNode.addRight(rightChildLev01);
Serializer<byte[]> serializer = Serializer.Bytes();
byte[] data = serializer.serialize(rootNode);
Node rootNodeDeserialized = serializer.deserialize(data);
System.out.println(rootNode.toString());
System.out.println(" ========== ");
System.out.println(rootNodeDeserialized.toString());
现在,让我们看看这是否也适用于Java对象图。为此,表示节点的类进行了更改,以便还可以定义一个父节点。现在可以建立循环。
public class GraphNode {
private String id;
private GraphNode parent;
private List<GraphNode> childGraphNodes = new ArrayList<>();
我们以此处列出的图为例。
GraphNode rootNode = new GraphNode("rootNode");
GraphNode child01Lev01 = new GraphNode("child01Lev01");
GraphNode child02Lev01 = new GraphNode("child02Lev01");
rootNode.addChildGraphNode(child01Lev01);
rootNode.addChildGraphNode(child02Lev01);
child01Lev01.setParent(rootNode);
child02Lev01.setParent(rootNode);
GraphNode child01Lev02 = new GraphNode("child01Lev02");
GraphNode child02Lev02 = new GraphNode("child02Lev02");
child01Lev01.addChildGraphNode(child01Lev02);
child01Lev01.addChildGraphNode(child02Lev02);
child01Lev02.setParent(child01Lev01);
child01Lev02.setParent(child01Lev01);
GraphNode child01Lev03 = new GraphNode("child01Lev03");
GraphNode child02Lev03 = new GraphNode("child02Lev03");
child01Lev03.setParent(child02Lev01);
child02Lev03.setParent(child02Lev01);
child02Lev01.addChildGraphNode(child01Lev03);
child02Lev01.addChildGraphNode(child02Lev03);
//creating cycles
rootNode.addChildGraphNode(child01Lev03);
rootNode.setParent(child02Lev03);
这个图也没有任何问题地被处理,循环也没有引起任何问题。
Serializer<byte[]> serializer = Serializer.Bytes();
byte[] data = serializer.serialize(rootNode);
GraphNode rootNodeDeserialized = serializer.deserialize(data);
您可以使示例变得更加复杂,并尝试继承的微妙之处。还支持JDK17中的新数据类型。这意味着我有一个强大的工具可以处理各种任务。例如,在另一个名为EclipseStore的Eclipse项目中可以找到一种用于持续性的机制,基于这种序列化。但是您自己的小项目也可以受益于此。我将展示如何将其快速集成到残余服务中。
如果您想在Java中创建一个简单的REST服务,用于传输字节流,而不使用Spring Boot,您可以使用Java SE API和com.sun.net.httpserver包中的HttpServer类,该类允许您创建一个HTTP服务器。
/api/bytestream
请求的上下文。ByteStreamHandler
类处理用于上传字节流的POST
请求和用于下载字节流的GET
请求。POST
请求,它读取传入的字节流,根据需要进行处理,并发送一个响应。GET
请求,它发送一个预定义的字节流作为响应。请记住,这只是一个简单的例子,您可以根据需要扩展它来处理更复杂的用例和错误处理,以满足您特定应用程序的需求。还要注意,com.sun.net.httpserver包是JDK的一部分,但它可能不会在所有Java发行版中都可用。
public class ByteStreamHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
String requestMethod = exchange.getRequestMethod();
if (requestMethod.equalsIgnoreCase("POST")) {
// Handle POST requests for uploading byte streams
handleUpload(exchange);
} else if (requestMethod.equalsIgnoreCase("GET")) {
// Handle GET requests for downloading byte streams
handleDownload(exchange);
}
}
private void handleUpload(HttpExchange exchange) throws IOException {
// Get the input stream from the request
InputStream inputStream = exchange.getRequestBody();
// Read the byte stream and process it as needed
ByteArrayOutputStream byteArrayOutputStream
= new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, bytesRead);
}
// Process the uploaded byte stream
// (e.g., save to a file or perform other actions)
byte[] data = byteArrayOutputStream.toByteArray();
// Do something with 'data'
Serializer<byte[]> serializer = Serializer.Bytes();
GraphNode deserialized = serializer.deserialize(data);
//process the data
System.out.println("deserialized = " + deserialized);
// Send a response (you can customize this)
String response = "Byte stream uploaded successfully.";
exchange.sendResponseHeaders(200, response.length());
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
}
private void handleDownload(HttpExchange exchange) throws IOException {
// Simulate generating and sending a byte stream as a response
String response = "Hello, Byte Stream!";
Serializer<byte[]> serializer = Serializer.Bytes();
byte[] data = serializer.serialize(response.getBytes());
exchange.sendResponseHeaders(200, data.length);
OutputStream os = exchange.getResponseBody();
os.write(data);
os.close();
}
}
我们审视了Java原始序列化的典型问题,以及使用开源的Eclipse序列化器项目从JVM到JVM进行通信时的繁琐实现是不必要的。在建模图形方面没有限制,因为它不仅处理了包括JDK17在内的当前新数据类型,图中的循环也不成问题。
使用Serializable接口也是不必要的,不影响处理。易于处理使得它可以用于细小的项目,例如使用内置JDK资源的REST服务。使用序列化器的大型项目包括开源项目EclipseStore。该项目提供了一个针对JVM的高性能持久性机制。
祝编码愉快
斯文
推荐阅读: 39.SpringBoot 打成的jar包和普通的jar包有什么区别 ?
本文链接: EclipseStore 高性能序列化器