跳到主要内容
版本:dev

Java 原生序列化

Java 原生序列化是通过 withXlang(false) 选择的仅限 Java 编码格式。当所有写入端和读取端都是 Java/JVM 进程,并且载荷应遵循 JVM 类型系统而非可移植的跨语言类型系统时,请使用该模式。对于仅限 Java/JVM、用于替代 JDK 序列化、Kryo、FST、Hessian 或仅限 Java 的 Protocol Buffers 载荷的场景,原生序列化是合适的起点。

本页所称原生序列化是指 Fory 的 xlang=false 编码模式,与 GraalVM 原生镜像支持不同;后者参见 GraalVM 原生镜像

如果字节需要由非 Java Fory 实现读取,请使用 Java 默认的跨语言序列化模式。

何时使用原生序列化

以下情况使用原生序列化:

  • 载荷仅由 Java/JVM 应用生成和使用。
  • 对象模型使用 Java 专用类型、JDK 集合、包装类型、继承、接口或多态,并且不需要跨语言 Schema。
  • 现有类依赖 writeObjectreadObjectwriteReplacereadResolvereadObjectNoDataExternalizable
  • 需要通过 Fory.copy(...) 复制 Java 对象。
  • 大型原始类型数组或二进制载荷应使用原生模式带外缓冲区。
  • 正在替代仅限 Java 的序列化框架,并希望支持最广泛的 Java 对象。

如果载荷必须由 Python、C++、Go、Rust、JavaScript/TypeScript、C#、Swift、Dart、Scala、Kotlin 或其他非 Java 实现读取,请改用跨语言序列化。

创建原生模式 Fory 实例

import org.apache.fory.Fory;

Fory fory = Fory.builder()
.withXlang(false)
.requireClassRegistration(true)
.withRefTracking(true)
.build();

byte[] bytes = fory.serialize(object);
Object decoded = fory.deserialize(bytes);

为每种配置创建并复用一个 ForyThreadSafeFory 实例。Fory 会缓存类元数据、序列化器和生成代码,因此创建实例的开销不低。

import org.apache.fory.Fory;
import org.apache.fory.ThreadSafeFory;

ThreadSafeFory fory = Fory.builder()
.withXlang(false)
.requireClassRegistration(true)
.withRefTracking(true)
.buildThreadSafeFory();

fory.register(Order.class, 100);

在启动期间、并发序列化开始前注册类和序列化器。类加载器、注册、安全、Schema 演进或引用跟踪设置不同时,应使用独立的 Fory 实例。

Schema 演进

原生序列化默认使用兼容模式,因此只要 Schema 元数据保持兼容,读取端就能容忍滚动发布期间的 Schema 变化:

Fory fory = Fory.builder()
.withXlang(false)
.build();

兼容模式允许读取端在 Schema 元数据保持兼容时容忍字段新增、删除或重排,并且默认启用元数据共享。字段 ID、类版本检查、元数据共享和未知类处理参见 Schema 演进

仅当每个读取端和写入端始终使用相同的类 Schema 时,才设置 .withCompatible(false) 以获得更快速度和更小体积。

注册与安全

类注册默认启用。在服务边界保持启用,并显式注册应用类:

Fory fory = Fory.builder()
.withXlang(false)
.requireClassRegistration(true)
.build();

fory.register(Order.class, 100);
fory.register(LineItem.class, 101);

显式数字 ID 可以避免注册顺序漂移。如果使用不带 ID 的 fory.register(MyClass.class),每个写入端和读取端都必须按相同顺序注册类。类型 ID 难以协调时,也可按名称注册:

fory.register(Order.class, "com.example", "Order");

仅在可信环境中禁用类注册。如果需要动态类加载,请安装 TypeCheckerAllowListChecker,让反序列化能够拒绝意外类:

import org.apache.fory.Fory;
import org.apache.fory.resolver.AllowListChecker;

AllowListChecker checker = new AllowListChecker(AllowListChecker.CheckLevel.STRICT);
checker.allowClass("com.example.*");

Fory fory = Fory.builder()
.withXlang(false)
.requireClassRegistration(false)
.withTypeChecker(checker)
.withMaxDepth(100)
.build();

对不可信或外部提供的载荷使用 withMaxDepth(...) 限制对象图深度。完整安全配置参见类型注册

Java 对象支持范围

原生序列化负责以下 Java 专用对象能力:

  • POJO、record、枚举、原始类型数组、对象数组和常见 JDK 集合。
  • 继承、接口、多态字段、共享引用和循环对象图。
  • 无需映射为可移植跨语言类型的 Java 包装器和集合行为。
  • 需要 Java 序列化兼容性的类所使用的 JDK 序列化钩子。
  • 通过 registerSerializer(...)registerSerializerAndType(...) 注册的自定义序列化器。

对于普通应用类,Fory 可以使用生成的序列化器并避免 JDK ObjectOutputStream 语义。需要 JDK 序列化钩子的类可以使用 Java 序列化兼容路径;如果基于钩子的路径开销过大,热点类应优先使用 Fory 自定义序列化器。

JDK 自定义序列化

依赖 JDK 序列化钩子的类参见专门的 JDK 自定义序列化指南。Fory 在原生模式下遵循这些对象钩子,但 Fory 字节不是 ObjectOutputStream 字节。

对象图与引用跟踪

启用引用跟踪时,原生模式支持共享引用和循环引用:

Fory fory = Fory.builder()
.withXlang(false)
.withRefTracking(true)
.build();

仅对对象标识和循环不属于数据模型的值形对象图禁用引用跟踪:

Fory fory = Fory.builder()
.withXlang(false)
.withRefTracking(false)
.build();

引用跟踪是一项语义选择。关闭它可以提升性能并减小载荷体积,但重复引用会反序列化为不同对象,而且不支持循环。

对象复制

Fory 可以在不实例化字节数组的情况下深拷贝 Java 对象。完整复制语义、自定义复制钩子和故障排除参见对象复制

Fory fory = Fory.builder()
.withXlang(false)
.withRefCopy(true)
.build();

MyClass copy = fory.copy(original);

withRefCopy(true) 控制复制操作的引用保留,与控制序列化和反序列化的 withRefTracking(...) 相互独立。

零拷贝序列化

原生模式支持将大型二进制值和原始类型数组作为带外 BufferObject 载荷:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.fory.Fory;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.serializer.BufferObject;

Fory fory = Fory.builder()
.withXlang(false)
.build();

List<Object> value = Arrays.asList("str", new byte[1000], new int[100], new double[100]);
Collection<BufferObject> bufferObjects = new ArrayList<>();
byte[] bytes = fory.serialize(value, bufferObject -> !bufferObjects.add(bufferObject));
List<MemoryBuffer> buffers = bufferObjects.stream()
.map(BufferObject::toBuffer)
.collect(Collectors.toList());

Object decoded = fory.deserialize(bytes, buffers);

对于应带外发送的缓冲区,回调返回 false。主字节数组仍包含根对象图,并按回调顺序引用这些缓冲区。

当传输层可以分别承载主载荷和缓冲区时使用此方式。如果流作为单个字节数组存储或发送,请省略回调,让 Fory 将缓冲区内容保留在带内。

原生序列化还支持字节数组、MemoryBufferByteBufferOutputStreamForyInputStreamForyReadableChannel API。请选择与现有边界匹配的 API;已有缓冲区或流时,应避免通过 byte[] 复制。

类加载器

ClassLoader loader = Thread.currentThread().getContextClassLoader();

Fory fory = Fory.builder()
.withXlang(false)
.withClassLoader(loader)
.build();

由于类元数据和序列化器会被缓存,每个 Fory 实例都绑定到一个类加载器。应为每个应用、插件或租户类加载器构建独立 Fory 实例,不要在现有实例上切换加载器。

性能指南

  • 复用 ForyThreadSafeFory 实例,不要为每个请求重新构建。

  • 使用显式数字 ID 注册类,以获得紧凑的类型元数据和稳定的部署。

  • 仅当每个读取端和写入端始终使用相同的类 Schema,并且应用希望获得更快速度和更小体积时,才使用 .withCompatible(false)

  • 对不包含对象标识或循环的值形对象图禁用引用跟踪。

  • 普通 JVM 的启动延迟可以容忍先使用解释器序列化时,可使用异步编译:

    Fory fory = Fory.builder()
    .withXlang(false)
    .withAsyncCompilation(true)
    .build();
  • 在普通 JVM 上保持运行时代码生成启用。GraalVM 原生镜像和 Android 流程使用静态生成的序列化器。

  • 传输层支持拆分载荷时,为大型原始类型数组或二进制字段使用零拷贝带外缓冲区。

  • 对象契约允许时,为热点类使用 Fory 自定义序列化器替代昂贵的 JDK 序列化钩子。

原生模式与跨语言模式对比

需求使用原生序列化使用跨语言序列化
仅限 Java/JVM 的载荷可选
非 Java 读取端或写入端
广泛的 Java 对象支持仅限跨语言类型
JDK 序列化钩子
Java 对象复制
跨语言可移植类型映射
默认兼容 Schema 演进
相同 Schema 性能优化

故障排除

非 Java 实现无法读取载荷

写入端正在使用原生序列化。请使用 .withXlang(true) 重新构建写入端,并与每个对等端对齐类型注册。

反序列化期间类被拒绝

保持类注册启用,并在写入端和读取端都注册该类。如果确实需要动态类加载,仅在使用 requireClassRegistration(false) 的同时配置允许列表 TypeChecker

字段变更后滚动发布失败

原生序列化默认使用兼容模式。写入端和读取端版本可能不同时,请保留该默认设置,并为长期使用的 Schema 添加稳定字段元数据。

未保留对象标识

为序列化和反序列化启用 .withRefTracking(true);对于 Fory.copy(...),启用 .withRefCopy(true)

迁移边界同时接收 JDK 和 Fory 字节

仅在混合格式边界使用 JavaSerializer.serializedByJDK(...),随后将 JDK 字节交给 ObjectInputStream,将 Fory 原生字节交给 fory.deserialize(...)

相关主题