Apache Fory 快速入门与集成示例

Apache Fory 快速入门与集成示例:从环境准备、首个序列化示例与结果解读入手,对照 JDK 原生序列化,给出集合嵌套与循环引用处理,并展示在 RPC、缓存、消息队列、跨语言微服务中集成 Fory 的样例代码与真实字节对比。

Apache ForyApache Fory2026/10/12

Apache Fory 快速入门与集成示例

目标:用最短路径跑通 Apache Fory,理解序列化结果,并把它集成进 RPC、缓存、消息队列与跨语言微服务四类常见场景。文中真实回显来自 Ubuntu 22.04(OpenJDK 17)与 CentOS 8.4(OpenJDK 11)两台机器的验证。

一、快速入门

1.1 环境准备

需要 JDK 8+(本文用 JDK 11/17)与 Maven。在 pom.xml 中加入依赖:

<dependency>
  <groupId>org.apache.fory</groupId>
  <artifactId>fory-core</artifactId>
  <version>1.7.6</version>
</dependency>

1.2 第一个示例

package com.example;

import org.apache.fory.Fory;
import org.apache.fory.config.Language;

public class QuickStart {

  public static class User implements java.io.Serializable {
    public String name;
    public int age;
    public User() {}
    public User(String name, int age) { this.name = name; this.age = age; }
    @Override public String toString() { return "User{name=" + name + ", age=" + age + "}"; }
  }

  public static void main(String[] args) {
    Fory fory = Fory.builder()
        .withLanguage(Language.JAVA)
        .requireClassRegistration(false)
        .build();

    User user = new User("Alice", 30);
    byte[] bytes = fory.serialize(user);
    User back = (User) fory.deserialize(bytes);

    System.out.println("original     : " + user);
    System.out.println("deserialized : " + back);
    System.out.println("Fory bytes   : " + bytes.length);
  }
}

编译并运行:

cd /root/fory-lab
CP=$(cat cp.txt)
javac -cp "$CP" -d target/classes src/main/java/com/example/QuickStart.java
java -Xmx384m -cp "target/classes:$CP" com.example.QuickStart

真实回显:

original     : User{name=Alice, age=30}
deserialized : User{name=Alice, age=30}
Fory bytes   : 53

1.3 结果解读

  • serialize 返回 byte[],deserialize 还原对象,往返内容一致;
  • 仅 String name 与 int age 两个字段的对象序列化后 53 字节;
  • .requireClassRegistration(false) 用于快速上手;生产环境应改为 fory.register(User.class)(见第八节)。

二、与 JDK 原生序列化的对照

对同一对象分别用 Fory 与 JDK ObjectOutputStream 序列化,真实回显:

original     : User{name=Alice, age=30}
deserialized : User{name=Alice, age=30}
content equal: true
Fory bytes   : 53
JDK  bytes   : 93

可见 Fory 字节数约为 JDK 原生序列化的 57%。对更复杂的对象差距更大(见下节)。

三、集合、嵌套对象与循环引用

3.1 嵌套对象与集合

定义一个含嵌套对象、List、Map 的 Order:

public static class Order implements java.io.Serializable {
  public String orderId;
  public User buyer;
  public List<String> items = new ArrayList<>();
  public Map<String, Integer> qty = new HashMap<>();
  public Order() {}
}

真实回显:

---- Case2 nested/collection ----
original     : orderId=ORD-2026-0001, buyer=User{name=Bob, age=41, email=bob@example.com, vip=false}, items=[apple, banana, cherry], qty={apple=3, banana=5}
deserialized : orderId=ORD-2026-0001, buyer=User{name=Bob, age=41, email=bob@example.com, vip=false}, items=[apple, banana, cherry], qty={apple=3, banana=5}
content equal: true
Fory bytes   : 203
JDK  bytes   : 599

嵌套结构下 Fory 为 203 字节,JDK 为 599 字节(约 34%)。

3.2 循环引用

循环引用必须开启引用跟踪,否则会无限递归:

Fory fory = Fory.builder()
    .withLanguage(Language.JAVA)
    .withRefTracking(true)          // 循环引用必需
    .requireClassRegistration(false)
    .build();

Node a = new Node("A");
Node b = new Node("B");
a.next = b;
b.next = a;                          // 构成闭环
Node a2 = (Node) fory.deserialize(fory.serialize(a));

真实回显:

---- Case3 circular reference ----
a.id=A, a.next.id=B, a.next.next.id=A
identity preserved (a2.next.next == a2): true
Fory bytes   : 54

闭环成功还原,且 a2.next.next 与 a2 是同一实例。

3.3 线程安全实例

多线程共享时使用 ThreadSafeFory:

ThreadSafeFory tsFory = Fory.builder()
    .withLanguage(Language.JAVA)
    .requireClassRegistration(false)
    .buildThreadSafeFory();

byte[] b = tsFory.serialize(user);
User u = tsFory.deserialize(b, User.class);

真实回显:

---- Case4 ThreadSafeFory ----
deserialized : User{name=Alice, age=30, email=alice@example.com, vip=true}
content equal: true
Fory bytes   : 84

四、集成到 RPC 框架

在 RPC 场景中,把 Fory 作为请求/响应体的序列化器即可替换 JDK 序列化或 JSON:

public final class ForyCodec {
  // 每个业务类注册一次,避免每次写类名,同时满足默认的类注册校验
  private static final ThreadSafeFory FORY = create();

  private static ThreadSafeFory create() {
    ThreadSafeFory f = Fory.builder()
        .withLanguage(Language.JAVA)
        .withRefTracking(true)
        .buildThreadSafeFory();
    f.register(Request.class);
    f.register(Response.class);
    return f;
  }

  public static byte[] encode(Object msg) {
    return FORY.serialize(msg);
  }

  public static <T> T decode(byte[] bytes, Class<T> type) {
    return FORY.deserialize(bytes, type);
  }
}

在 RPC 框架的自定义编解码器中,把 encode 的输出作为消息体、decode 在服务端还原即可。同一次调用中的请求与响应均受益于更小的传输体积。

五、集成到缓存层

缓存常需把对象序列化为字节后写入 Redis/本地缓存。用 Fory 可显著降低存储与网络开销:

public class ForyCacheSerializer {
  private static final Fory FORY = Fory.builder()
      .withLanguage(Language.JAVA)
      .withRefTracking(true)
      .requireClassRegistration(false)
      .build();

  public <T> byte[] toBytes(T value) {
    return FORY.serialize(value);
  }

  @SuppressWarnings("unchecked")
  public <T> T fromBytes(byte[] bytes, Class<T> type) {
    return (T) FORY.deserialize(bytes);
  }
}

实测对照:同一条缓存值(Order 示例)Fory 203 字节、JDK 599 字节,单条节省约 66% 空间;在高频读写场景可成比例降低带宽与内存占用。

六、集成到消息队列

消息体的序列化可用 Fory 的流式接口直接写入 MQ 的字节数组:

public class ForyMessageProducer {

  private final Fory fory = newFory();

  private static Fory newFory() {
    Fory f = Fory.builder()
        .withLanguage(Language.JAVA)
        .withRefTracking(true)
        .requireClassRegistration(false)
        .build();
    f.register(OrderEvent.class);
    return f;
  }

  public byte[] buildMessage(OrderEvent event) {
    return fory.serialize(event);
  }
}

若队列两端运行不同版本的应用,可改用兼容模式以保证字段演进时仍能解析:

Fory fory = Fory.builder()
    .withLanguage(Language.JAVA)
    .withCompatibleMode(CompatibleMode.COMPATIBLE)   // 允许字段增删
    .requireClassRegistration(false)
    .build();

注意:兼容模式会携带字段元信息,体积略大于强一致模式(实测同一对象 94 vs 66 字节),需在兼容性与体积之间权衡。

七、跨语言微服务集成

Java 服务与 Python/C++/Go 服务交换数据时使用 XLANG 模式,并显式注册类型名:

Fory fory = Fory.builder()
    .withLanguage(Language.XLANG)
    .build();
fory.register(Order.class, "example.Order");   // 跨语言类型名需两端一致

byte[] bytes = fory.serialize(order);
Order back = fory.deserialize(bytes, Order.class);

真实回显:

[xlang] OK bytes=68 -> User{name=Alice, age=30, email=alice@example.com, vip=true}

要点:

  • 两端必须使用相同的类型名字符串(如 example.Order);
  • XLANG 模式下引用类型字段默认非空,可空字段需加 @Nullable,否则报 Non-nullable field ... is null;
  • 类型名通过 register(Class, String) 声明,而非 Java 全限定名,屏蔽各语言类名差异。

八、性能与安全建议

建议说明
保持默认 requireClassRegistration(true)防止反序列化漏洞;未注册类会抛 InsecureException
对业务类提前 register省略类名,体积更小(实测 94 → 75 字节),也更快
循环/共享引用开 withRefTracking(true)否则闭环对象会栈溢出
长期存储/跨版本消息用 COMPATIBLE支持字段演进
内部高性能 RPC 用 SCHEMA_CONSISTENT体积最小、速度最快
多线程共享用 ThreadSafeFory单个 Fory 实例非线程安全
JVM 堆保持 256–512 MB示例统一 -Xmx384m
JDK 17+ 补充 --add-opens避免模块反射访问告警

九、常见问题

Q1:直接照抄 Fory.builder().withLanguage(Language.JAVA).build() 为何报错?

默认 requireClassRegistration=true,未注册的类会抛 InsecureException。请先 fory.register(YourClass.class),或(仅限可信环境)设置 .requireClassRegistration(false)。

Q2:循环引用导致 StackOverflowError?

未开启引用跟踪。加上 .withRefTracking(true)。

Q3:跨语言反序列化报字段为 null 不合法?

XLANG 模式字段默认非空,为可空字段加 @Nullable。

Q4:Fory 与 JDK 原生序列化能否互通?

不能。Fory 使用自有线格式,两端都需使用 Fory(或对应语言的 Fory 实现)。若需替换 JDK 序列化,需两端一起切换。

Q5:如何为已有对象选择合适的兼容模式?

两端同步发布、字段稳定时用 SCHEMA_CONSISTENT;存在灰度、跨版本消息或长期持久化时用 COMPATIBLE。