跳到主要内容
版本:dev

字段配置

本页说明如何在 Java 中配置序列化字段级元信息。

概述

Apache ForyTM 通过注解提供字段级配置:

  • @ForyField:配置字段元信息(id、nullable、ref、dynamic)
  • @Ignore:将字段排除在序列化之外
  • 整数类型注解:控制整数编码方式(varint、fixed、tagged、unsigned)

这些能力可用于:

  • Tag ID:为兼容模式分配紧凑数值 ID,降低 struct 字段元信息开销
  • 可空控制:声明字段是否允许为 null
  • 引用跟踪:为共享对象开启引用跟踪
  • 字段跳过:显式排除不需要序列化的字段
  • 编码控制:指定整数序列化编码策略
  • 多态控制:控制 struct 字段是否写入运行时类型信息

基本语法

在字段上使用注解:

import org.apache.fory.annotation.ForyField;

public class Person {
@ForyField(id = 0)
private String name;

@ForyField(id = 1)
private int age;

@ForyField(id = 2, nullable = true)
private String nickname;
}

@ForyField 注解

使用 @ForyField 配置字段级元信息:

public class User {
@ForyField(id = 0)
private long id;

@ForyField(id = 1)
private String name;

@ForyField(id = 2, nullable = true)
private String email;

@ForyField(id = 3, ref = true)
private List<User> friends;

@ForyField(id = 4, dynamic = ForyField.Dynamic.TRUE)
private Object data;
}

参数

参数类型默认值说明
idint-1字段 tag ID(-1 表示使用字段名编码)
nullablebooleanfalse字段是否可为 null
refbooleanfalse是否开启引用跟踪
dynamicDynamicAUTO控制 struct 字段多态行为

字段 ID(id

通过为字段分配数值 ID,可降低兼容模式下 struct 字段元信息开销:

public class User {
@ForyField(id = 0)
private long id;

@ForyField(id = 1)
private String name;

@ForyField(id = 2)
private int age;
}

收益:

  • 序列化体积更小(元信息里用数值 ID 而不是字段名)
  • struct 字段元信息开销更低
  • 可在不破坏二进制兼容性的前提下重命名字段

建议: 兼容模式下建议配置字段 ID,以降低序列化成本。

注意:

  • 同一个类内 ID 必须唯一
  • ID 必须 >= 0-1 表示使用字段名编码,也是默认行为)
  • 未指定时,元信息将写字段名(开销更大)

不配置字段 ID(元信息使用字段名)示例:

public class User {
private long id;
private String name;
}

可空字段(nullable

对可能为 null 的字段使用 nullable = true

public class Record {
// 可空字符串字段
@ForyField(id = 0, nullable = true)
private String optionalName;

// 可空 Integer 字段(装箱类型)
@ForyField(id = 1, nullable = true)
private Integer optionalCount;

// 非可空字段(默认)
@ForyField(id = 2)
private String requiredName;
}

注意:

  • 默认是 nullable = false(不可空)
  • nullable = false 时,Fory 会省略 null 标记写入(节省 1 字节)
  • 可能为 null 的装箱类型(IntegerLong 等)建议显式设为 nullable = true

引用跟踪(ref

对于可能共享或循环引用的字段,启用引用跟踪:

public class RefOuter {
// 两个字段可能指向同一个内部对象
@ForyField(id = 0, ref = true, nullable = true)
private RefInner inner1;

@ForyField(id = 1, ref = true, nullable = true)
private RefInner inner2;
}

public class CircularRef {
@ForyField(id = 0)
private String name;

// 自引用字段,用于循环引用
@ForyField(id = 1, ref = true, nullable = true)
private CircularRef selfRef;
}

适用场景:

  • 字段可能形成循环或共享关系
  • 同一对象被多个字段引用

注意:

  • 默认是 ref = false(不跟踪引用)
  • ref = false 可避免 IdentityMap 开销,也会跳过引用跟踪标记
  • 仅在全局启用 ref tracking 时,字段级 ref 才生效

Dynamic(多态控制)

控制跨语言序列化时 struct 字段的多态行为:

public class Container {
// AUTO:接口/抽象类型动态,具体类型非动态
@ForyField(id = 0, dynamic = ForyField.Dynamic.AUTO)
private Animal animal; // 接口类型,写入类型信息

// FALSE:不写类型信息,直接按声明类型序列化
@ForyField(id = 1, dynamic = ForyField.Dynamic.FALSE)
private Dog dog; // 具体类型,不写类型信息

// TRUE:写类型信息,支持运行时子类型
@ForyField(id = 2, dynamic = ForyField.Dynamic.TRUE)
private Object data; // 强制多态
}

取值:

说明
AUTO自动判断:接口/抽象类型动态,具体类型非动态
FALSE不写类型信息,直接使用声明类型的序列化器
TRUE写入类型信息,以支持运行时子类型

跳过字段

使用 @Ignore

将字段排除在序列化之外:

import org.apache.fory.annotation.Ignore;

public class User {
@ForyField(id = 0)
private long id;

@ForyField(id = 1)
private String name;

@Ignore
private String password; // 不序列化

@Ignore
private Object internalState; // 不序列化
}

使用 transient

Java 的 transient 关键字同样会排除字段:

public class User {
@ForyField(id = 0)
private long id;

private transient String password; // 不序列化
private transient Object cache; // 不序列化
}

整数类型注解

Fory 提供整数注解,用于在跨语言场景中控制编码方式。

有符号 32 位整数(@Int32Type

import org.apache.fory.annotation.Int32Type;

public class MyStruct {
// 变长编码(默认),小值更紧凑
@Int32Type(compress = true)
private int compactId;

// 固定 4 字节编码,长度稳定
@Int32Type(compress = false)
private int fixedId;
}

有符号 64 位整数(@Int64Type

import org.apache.fory.annotation.Int64Type;
import org.apache.fory.config.LongEncoding;

public class MyStruct {
// 变长编码(默认)
@Int64Type(encoding = LongEncoding.VARINT)
private long compactId;

// 固定 8 字节编码
@Int64Type(encoding = LongEncoding.FIXED)
private long fixedTimestamp;

// tagged 编码(小值 4 字节,否则 9 字节)
@Int64Type(encoding = LongEncoding.TAGGED)
private long taggedValue;
}

无符号整数

import org.apache.fory.annotation.Uint8Type;
import org.apache.fory.annotation.Uint16Type;
import org.apache.fory.annotation.Uint32Type;
import org.apache.fory.annotation.Uint64Type;
import org.apache.fory.config.LongEncoding;

public class UnsignedStruct {
// 无符号 8 位 [0, 255]
@Uint8Type
private short flags;

// 无符号 16 位 [0, 65535]
@Uint16Type
private int port;

// 无符号 32 位,varint 编码(默认)
@Uint32Type(compress = true)
private long compactCount;

// 无符号 32 位,fixed 编码
@Uint32Type(compress = false)
private long fixedCount;

// 无符号 64 位,多种编码
@Uint64Type(encoding = LongEncoding.VARINT)
private long varintU64;

@Uint64Type(encoding = LongEncoding.FIXED)
private long fixedU64;

@Uint64Type(encoding = LongEncoding.TAGGED)
private long taggedU64;
}

编码汇总

注解Type ID编码大小
@Int32Type(compress = true)5varint1-5 字节
@Int32Type(compress = false)4fixed4 字节
@Int64Type(encoding = VARINT)7varint1-10 字节
@Int64Type(encoding = FIXED)6fixed8 字节
@Int64Type(encoding = TAGGED)8tagged4 或 9 字节
@Uint8Type9fixed1 字节
@Uint16Type10fixed2 字节
@Uint32Type(compress = true)12varint1-5 字节
@Uint32Type(compress = false)11fixed4 字节
@Uint64Type(encoding = VARINT)14varint1-10 字节
@Uint64Type(encoding = FIXED)13fixed8 字节
@Uint64Type(encoding = TAGGED)15tagged4 或 9 字节

何时使用:

  • varint:数值通常较小时最优(默认)
  • fixed:数值分布接近全范围时更稳定(如时间戳、哈希)
  • tagged:在体积与性能间平衡较好
  • 无符号类型:适合与 Rust/Go/C++ 等语言进行无符号整数互操作

完整示例

import org.apache.fory.Fory;
import org.apache.fory.annotation.ForyField;
import org.apache.fory.annotation.Ignore;
import org.apache.fory.annotation.Int64Type;
import org.apache.fory.annotation.Uint64Type;
import org.apache.fory.config.LongEncoding;

import java.util.List;
import java.util.Map;
import java.util.Set;

public class Document {
// 带 tag ID 的字段(兼容模式推荐)
@ForyField(id = 0)
private String title;

@ForyField(id = 1)
private int version;

// 可空字段
@ForyField(id = 2, nullable = true)
private String description;

// 集合字段
@ForyField(id = 3)
private List<String> tags;

@ForyField(id = 4)
private Map<String, String> metadata;

@ForyField(id = 5)
private Set<String> categories;

// 不同编码方式的整数
@ForyField(id = 6)
@Uint64Type(encoding = LongEncoding.VARINT)
private long viewCount; // varint 编码

@ForyField(id = 7)
@Uint64Type(encoding = LongEncoding.FIXED)
private long fileSize; // fixed 编码

@ForyField(id = 8)
@Uint64Type(encoding = LongEncoding.TAGGED)
private long checksum; // tagged 编码

// 共享/循环引用字段
@ForyField(id = 9, ref = true, nullable = true)
private Document parent;

// 忽略字段(不序列化)
private transient Object cache;

// Getters and setters...
}

// Usage
public class Main {
public static void main(String[] args) {
Fory fory = Fory.builder()
.withXlang(true)
.withCompatible(true)
.withRefTracking(true)
.build();

fory.register(Document.class, 100);

Document doc = new Document();
doc.setTitle("My Document");
doc.setVersion(1);
doc.setDescription("A sample document");

// Serialize
byte[] data = fory.serialize(doc);

// Deserialize
Document decoded = (Document) fory.deserialize(data);
}
}

跨语言兼容

当数据需要被其他语言(Python、Rust、C++、Go)读取时,建议使用字段 ID 并配套类型注解:

public class CrossLangData {
// 使用字段 ID 保证跨语言兼容
@ForyField(id = 0)
@Int32Type(compress = true)
private int intVar;

@ForyField(id = 1)
@Uint64Type(encoding = LongEncoding.FIXED)
private long longFixed;

@ForyField(id = 2)
@Uint64Type(encoding = LongEncoding.TAGGED)
private long longTagged;

@ForyField(id = 3, nullable = true)
private String optionalValue;
}

Schema 演进

兼容模式支持 Schema 演进。建议配置字段 ID 以降低序列化成本:

// Version 1
public class DataV1 {
@ForyField(id = 0)
private long id;

@ForyField(id = 1)
private String name;
}

// Version 2: Added new field
public class DataV2 {
@ForyField(id = 0)
private long id;

@ForyField(id = 1)
private String name;

@ForyField(id = 2, nullable = true)
private String email; // New field
}

V1 写入的数据可由 V2 读取(新增字段会是 null)。

也可以不配置字段 ID(元信息会使用字段名,开销更大):

public class Data {
private long id;
private String name;
}

Native 模式与 Xlang 模式

字段配置在不同序列化模式下的默认行为不同。

Native 模式(仅 Java)

Native 模式以最大兼容性为目标,默认值更宽松:

  • Nullable:引用类型默认可空
  • Ref tracking:对象引用默认开启(String、装箱类型、时间类型除外)
  • Polymorphism:所有非 final 类默认支持多态

在 Native 模式下,通常不需要额外字段注解,除非你希望:

  • 用字段 ID 降低体积
  • 关闭不必要的 ref 跟踪以优化性能
  • 对特定字段精确控制整数编码
// Native mode: works without any annotations
public class User {
private long id;
private String name;
private List<String> tags; // Nullable and ref-tracked by default
}

Xlang 模式(跨语言)

由于不同语言类型系统差异,Xlang 模式默认值更严格:

  • Nullable:字段默认不可空(nullable = false
  • Ref tracking:默认关闭(ref = false
  • Polymorphism:具体类型默认不写多态类型信息

在 Xlang 模式下,以下情况需要显式配置:

  • 字段可能为 null(使用 nullable = true
  • 字段需要共享/循环引用语义(使用 ref = true
  • 整数类型需要为跨语言兼容指定编码
  • 你希望减少元信息大小(使用字段 ID)
// Xlang mode: explicit configuration required for nullable/ref fields
public class User {
@ForyField(id = 0)
private long id;

@ForyField(id = 1)
private String name;

@ForyField(id = 2, nullable = true) // Must declare nullable
private String email;

@ForyField(id = 3, ref = true, nullable = true) // Must declare ref for shared objects
private User friend;
}

默认值汇总

选项Native 模式默认值Xlang 模式默认值
nullabletrue(引用类型)false
reftruefalse
dynamictrue(非 final 类)AUTO(具体类型通常按非动态处理)

最佳实践

  1. 配置字段 ID:兼容模式下建议配置,降低序列化成本
  2. 可空字段使用 nullable = true:对可为 null 的字段必须显式声明
  3. 共享对象开启 ref:对象共享或循环时使用 ref = true
  4. 敏感字段用 @Ignoretransient:如密码、令牌、内部状态
  5. 选择合适编码:小值用 varint,全范围值用 fixed
  6. 保持 ID 稳定:一旦分配,不要随意改动字段 ID
  7. 跨语言场景显式无符号类型:与 Rust、Go、C++ 互操作时尤其重要

注解速查

注解说明
@ForyField(id = N)配置字段 tag ID,减少元信息开销
@ForyField(nullable = true)将字段标记为可空
@ForyField(ref = true)为字段启用引用跟踪
@ForyField(dynamic = ...)控制 struct 字段多态行为
@Ignore将字段排除在序列化之外
@Int32Type(compress = ...)32 位有符号整数编码
@Int64Type(encoding = ...)64 位有符号整数编码
@Uint8Type8 位无符号整数
@Uint16Type16 位无符号整数
@Uint32Type(compress = ...)32 位无符号整数编码
@Uint64Type(encoding = ...)64 位无符号整数编码

相关主题