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