Apache Fory 团队很高兴宣布 1.2.0 版本正式发布。本版本包含来自 9 位贡献者的 38 个 PR,并继续改进各支持语言的跨语言运行时。请访问 Install 页面 获取各平台安装方式。
发布亮点
- 扩展 Go、Rust、Kotlin、Scala、C# 和 JavaScript 的 gRPC 生成支持,其中 JavaScript 同时支持 Node.js 和浏览器 gRPC-Web。
- 改进跨语言兼容性,细化 register-by-name API,支持兼容模式下的标量字段读取转换,并让 native serialization 默认启用兼容模式。
- 强化 Java 平台支持,增加 Java 9/16
module-info生成,并为 JDK 25 移除sun.misc.Unsafe使用。 - 通过更多读取检查、deflater 泄漏修复以及更安全的 serializer/type-info 错误处理提升运行时安全性和鲁棒性。
- 优化兼容模式和 row-format 性能,包括更快的兼容读取、紧凑 row layout 缓存以及内联 custom-codec dispatch。
- 提升 Rust、C++ 和 service 生成质量,包括更好的标识符转义、名称冲突处理、嵌套容器引用处理和 map 代码生成。
Java 25+ 不再依赖 sun.misc.Unsafe
JDK 25 继续推动平台远离 sun.misc.Unsafe。Fory 1.2.0 为核心运行时增加了 Java 25 multi-release 实现,使应用在 JDK 25+ 上运行时,不再从 Fory 的有效 class graph 中解析 sun.misc.Unsafe。
该实现仍保留 JDK 8-24 上的快速路径;但在 JDK 25+ 上会从 jar 的 versioned 区域加载替换类。这些替换类将字段访问和 memory-buffer 原语迁移到 JVM 支持的机制,例如 VarHandle、MethodHandle、数组和 ByteBuffer。绕过构造器的对象分配也会被显式处理:在 JDK 25+ 上,过去依赖 Unsafe allocation 的类应提供可访问的无参构造器、使用 record 构造,或注册自定义 serializer。
这对 Java 用户是一个重要兼容性里程碑:应用运行在 Java 25 及之后版本时,Fory runtime 不再依赖已被终止弃用的 Unsafe API 路径。
兼容标量字段读取
兼容模式已经允许读写双方添加、删除和重排字段。Fory 1.2.0 将这个模型扩展到部分标量类型变更:当匹配到的顶层字段在 boolean、string、numeric 和 decimal 类型之间变化时,只要转换是无损的,reader 就可以完成反序列化。
例如,"123" 可以读取为整数字段,1 或 0 可以读取为 boolean 字段,boolean 可以读取为 1/0,数字或 decimal 可以读取为规范字符串;数字 widening 或 narrowing 只有在不丢失范围或精度时才会成功。无效字符串、越界值、有损 float/integer 转换以及开启引用跟踪的标量类型变更会在反序列化时报错。该转换只应用于匹配到的兼容字段,不应用于根值或集合元素。
下面示例展示 Rust、Python、Java 和 C++。兼容标量字段转换也支持 Fory 的所有兼容模式运行时:Java、Python、Rust、C++、Go、C#、Swift、Dart、JavaScript/TypeScript、Kotlin 和 Scala。
Java 和 Python 的 xlang 与 native serialization 默认都启用兼容模式,也可以显式配置:
Fory fory = Fory.builder()
.withXlang(true)
.withCompatible(true)
.build();
import pyfory
fory = pyfory.Fory(xlang=True, compatible=True)
native_fory = pyfory.Fory(xlang=False, compatible=True)
Rust 示例:
use fory::{Fory, ForyStruct};
#[derive(ForyStruct)]
struct MetricV1 {
value: i64,
}
#[derive(ForyStruct)]
struct MetricV2 {
value: String,
}
let mut writer = Fory::builder().xlang(true).compatible(true).build();
writer.register_by_name::<MetricV1>("example.Metric")?;
let mut reader = Fory::builder().xlang(true).compatible(true).build();
reader.register_by_name::<MetricV2>("example.Metric")?;
let bytes = writer.serialize(&MetricV1 { value: 42 })?;
let value: MetricV2 = reader.deserialize(&bytes)?;
assert_eq!(value.value, "42");
Python 示例:
from dataclasses import dataclass
import pyfory
@dataclass
class MetricV1:
value: pyfory.Int64
@dataclass
class MetricV2:
value: str
writer = pyfory.Fory(xlang=True, compatible=True)
writer.register(MetricV1, name="example.Metric")
reader = pyfory.Fory(xlang=True, compatible=True)
reader.register(MetricV2, name="example.Metric")
data = writer.dumps(MetricV1(42))
assert reader.loads(data).value == "42"
Java 示例:
public class MetricV1 {
public long value;
}
public class MetricV2 {
public String value;
}
Fory writer = Fory.builder().withXlang(true).withCompatible(true).build();
writer.register(MetricV1.class, "example", "Metric");
Fory reader = Fory.builder().withXlang(true).withCompatible(true).build();
reader.register(MetricV2.class, "example", "Metric");
MetricV1 source = new MetricV1();
source.value = 42L;
byte[] bytes = writer.serialize(source);
MetricV2 value = reader.deserialize(bytes, MetricV2.class);
assert value.value.equals("42");
C++ 示例:
#include <cassert>
#include <string>
#include "fory/serialization/fory.h"
using namespace fory::serialization;
struct MetricV1 {
int64_t value;
};
FORY_STRUCT(MetricV1, value);
struct MetricV2 {
std::string value;
};
FORY_STRUCT(MetricV2, value);
auto writer = Fory::builder().xlang(true).compatible(true).build();
auto reader = Fory::builder().xlang(true).compatible(true).build();
writer.register_struct<MetricV1>("example", "Metric");
reader.register_struct<MetricV2>("example", "Metric");
auto bytes = writer.serialize(MetricV1{42}).value();
auto value = reader.deserialize<MetricV2>(bytes).value();
assert(value.value == "42");
反方向同样适用,例如将 String 字段值 "42" 读取为 int64,前提是字符串满足 Fory 严格的有限十进制语法,并且目标范围可以精确表示该值。
生成 gRPC 支持
Fory 1.2.0 扩展了 compiler 生成的 gRPC service companion。生成的 service 使用标准 gRPC transport、channel、deadline、metadata、interceptor、status code 和 streaming shape,但 request/response 对象使用 Fory 编码,而不是 protobuf message bytes。当 RPC 两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且你希望使用 gRPC 运维语义与 Fory payload 编码时,可以使用这种模式。
生成 gRPC 现在覆盖 Java、Python、Go、Rust、C#、Scala、Kotlin 和 JavaScript/TypeScript。JavaScript 同时支持 Node.js gRPC 和浏览器 gRPC-Web client 生成。
下面示例使用同一个 schema:
package demo.greeter;
message HelloRequest {
string name = 1;
}
message HelloReply {
string reply = 1;
}
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
Rust 生成基于 tonic 的 service API 和 binding module:
foryc service.fdl --rust_out=./generated/rust --grpc
use demo_greeter::{HelloReply, HelloRequest};
use demo_greeter_service::Greeter;
use demo_greeter_service_grpc::greeter_client::GreeterClient;
use demo_greeter_service_grpc::greeter_server::GreeterServer;
tonic::transport::Server::builder()
.add_service(GreeterServer::new(MyGreeter::default()))
.serve(addr)
.await?;
let mut client = GreeterClient::connect("http://[::1]:50051").await?;
let reply = client.say_hello(HelloRequest { name: "Fory".into() }).await?;
Go 生成 grpc-go interface 和 Fory-backed CodecV2:
foryc service.fdl --go_out=./generated/go --grpc
server := grpc.NewServer(
grpc.ForceServerCodecV2(greeter.CodecV2{}),
)
greeter.RegisterGreeterServer(server, greeterService{})
client := greeter.NewGreeterClient(conn)
reply, err := client.SayHello(ctx, &greeter.HelloRequest{Name: "Fory"})
JavaScript 同时支持 Node.js gRPC 和浏览器 gRPC-Web:
foryc service.fdl --javascript_out=./generated/javascript --grpc --grpc-web
import * as grpc from "@grpc/grpc-js";
import { addGreeterService, createGreeterClient } from "./service_grpc";
import { createGreeterWebPromiseClient } from "./service_grpc_web";
addGreeterService(server, greeterHandlers);
const nodeClient = createGreeterClient(
"localhost:50051",
grpc.credentials.createInsecure(),
);
nodeClient.sayHello({ name: "Fory" }, callback);
const webClient = createGreeterWebPromiseClient("https://api.example.com");
const reply = await webClient.sayHello({ name: "Fory" });
浏览器 gRPC-Web 遵循 gRPC-Web transport 限制:支持 unary 和 server-streaming 方法;client-streaming 和 bidirectional streaming 仍属于 Node.js/native gRPC 形态。
Kotlin 生成 grpc-kotlin coroutine companion:
foryc service.fdl --kotlin_out=./generated/kotlin --grpc
class GreeterService : GreeterGrpcKt.GreeterCoroutineImplBase() {
override suspend fun sayHello(request: HelloRequest): HelloReply =
HelloReply(reply = "Hello, ${request.name}")
}
val server = ServerBuilder
.forPort(50051)
.addService(GreeterService())
.build()
.start()
val stub = GreeterGrpcKt.GreeterCoroutineStub(channel)
val reply = stub.sayHello(HelloRequest(name = "Fory"))
Scala 生成 Scala 3 grpc-java companion,并提供更符合 Scala 使用习惯的 client handle:
foryc service.fdl --scala_out=./generated/scala --grpc
final class GreeterService extends GreeterGrpc.GreeterImplBase {
override def sayHello(request: HelloRequest): HelloReply =
HelloReply(s"Hello, ${request.name}")
}
val server = ServerBuilder
.forPort(50051)
.addService(new GreeterService)
.build()
.start()
val client = GreeterGrpc.newClient(channel)
val reply = Await.result(client.sayHello(HelloRequest("Fory")).asFuture, 30.seconds)
生成的 gRPC companion 不会让核心 Fory 语言包强依赖 gRPC。应用按实际使用的 transport 添加依赖:Java 和 Scala 使用 grpc-java,Python 使用 grpcio,Go 使用 grpc-go,Rust 使用 tonic/bytes,C# 使用 .NET gRPC 包,JavaScript 使用 @grpc/grpc-js 或 grpc-web,Kotlin 使用 grpc-java/grpc-kotlin。
功能特性
- feat(java): add java9/16 module-info support,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3721
- refactor(format): inline custom-codec dispatch in row codecs,作者 @stevenschlansker,见 https://github.com/apache/fory/pull/3716
- perf(format): cache compact row layout per nested slot,作者 @stevenschlansker,见 https://github.com/apache/fory/pull/3717
- feat(java): remove sun.misc.Unsafe for jdk25,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3702
- feat(rust): support thread safe
Arc<dyn Any + Send + Sync>type,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3736 - refactor(rust): refactor sync send type,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3737
- feat(xlang): refine register by name api,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3739
- feat(xlang): support compatible scalar read conversions,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3740
- feat: default compatible mode for native serialization,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3742
- perf: optimize compatible mode read performance,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3743
- feat(compiler): handle Rust identifier escaping and name collisions,作者 @BaldDemian,见 https://github.com/apache/fory/pull/3744
- feat(go): implement grpc stub generation,作者 @ayush00git,见 https://github.com/apache/fory/pull/3698
- refactor(compiler): generate C++ unordered map for Fory map,作者 @BaldDemian,见 https://github.com/apache/fory/pull/3745
- feat(compiler): handle nested container ref pointer options in C++ compiler correctly,作者 @BaldDemian,见 https://github.com/apache/fory/pull/3735
- feat: add more read checks,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3748
- feat(compiler): support Rust gRPC code generation,作者 @BaldDemian,见 https://github.com/apache/fory/pull/3738
- feat(cpp): support struct property accessors,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3751
- feat(python): make scalar wire markers typing-friendly,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3756
- feat(kotlin): add kotlin grpc support,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3757
- feat(rust): make fory-derive generated code use exported api in fory rust lib,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3759
- feat(scala): add generated grpc service support for scala,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3762
- feat(csharp): add generated grpc support for C#,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3761
- feat(javascript): add javascript gRPC support for nodejs/browser,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3760
Bug 修复
- fix(go): return nil serializer on getTypeInfo err,作者 @ayush00git,见 https://github.com/apache/fory/pull/3719
- fix(benchmarks): uses outdated google-java-format, upgrade spotless,作者 @stevenschlansker,见 https://github.com/apache/fory/pull/3722
- fix(format): pass row body size, not full payload size, to BinaryRow.pointTo,作者 @stevenschlansker,见 https://github.com/apache/fory/pull/3715
- fix(java): fix deflater memory leak,作者 @MNTMDEV,见 https://github.com/apache/fory/pull/3726
- fix(compiler): handle nested container ref pointer options in Rust compiler correctly,作者 @BaldDemian,见 https://github.com/apache/fory/pull/3731
- fix(java): ignore non-Scala/Lombok-style default helper methods,作者 @mandrean,见 https://github.com/apache/fory/pull/3733
- fix(c++): std::unordered_map cannot be used in struct. (#3727),作者 @ruoruoniao,见 https://github.com/apache/fory/pull/3728
- fix(grpc): fix rust/go grpc support,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3753
- fix(cpp): align unsigned struct default encoding,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3754
其他改进
- chore(deps): fix vulnerable dependencies,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3741
- chore: Bump MessagePack from 2.5.187 to 2.5.301,作者 @dependabot[bot],见 https://github.com/apache/fory/pull/3750
- chore(deps): bump Go gRPC test dependencies,作者 @chaokunyang,见 https://github.com/apache/fory/pull/3763
新贡献者
- @MNTMDEV 在 https://github.com/apache/fory/pull/3726 中完成了首次贡献
- @ruoruoniao 在 https://github.com/apache/fory/pull/3728 中完成了首次贡献
完整变更日志: https://github.com/apache/fory/compare/v1.1.0...v1.2.0
