Protocol Buffers Java 运行时实战指南:代码生成、Maven/Android Lite/Bazel 集成与 Kotlin 支持
在 Java/Kotlin 项目中使用 Protocol Buffers 时,你需要理解三件事:如何用 protoc 生成 Java 代码、如何引入对应版本的运行时库(Maven/Bazel),以及何时应选择 Lite 运行时来压缩移动端包体积。本文以 protobuf 仓库中 java/README.md 为主线,结合仓库内 java/pom.xml、java/lite.md、examples/BUILD.bazel 等实际构建文件,完整讲解从代码生成到构建集成的全流程与版本兼容规则。
一、核心工作流:用 protoc 生成 Java 代码
使用 protobuf 处理 .proto 文件的第一步是获取协议编译器 protoc(安装方式见仓库顶层 README.md),然后用它为目标 .proto 文件生成 Java 代码:
$ protoc --java_out=${OUTPUT_DIR} path/to/your/proto/file
生成的 Java 文件包含进你的项目源码树后,还需要在构建系统中添加对 protobuf Java 运行时的依赖。这里有一条贯穿全文的版本约束原则:运行时库的版本号必须与 protoc 的版本号相同(或更新),否则会因代码生成器与运行时不匹配而出错。当前仓库开发版本可参考 version.json,其中 java 语言运行时标记为 4.37-dev;文档中给出的稳定版示例为 4.28.2。
二、Maven 集成
2.1 核心运行时 protobuf-java
Maven 项目中添加核心运行时依赖:
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version><!--version--></version>
</dependency>
将 <!--version--> 替换为 Maven 仓库中 protobuf-java 的实际版本号(如 4.28.2),并确保它不旧于你使用的 protoc。
从仓库结构看,java/pom.xml 是 Maven 多模块工程的父 POM(protobuf-parent,当前版本 4.37.0),其 <modules> 声明了六个子模块:
<modules>
<module>bom</module>
<module>lite</module>
<module>core</module>
<module>util</module>
<module>kotlin</module>
<module>kotlin-lite</module>
</modules>
对应关系非常清晰:core 即完整 Java 运行时(发布为 protobuf-java),lite 是 Lite 运行时(发布为 protobuf-javalite),util 提供 JSON 等扩展能力(发布为 protobuf-java-util),kotlin 与 kotlin-lite 分别是 Kotlin 支持及其 Lite 配对包,bom 则用于依赖版本统一管理。
2.2 扩展包 protobuf-java-util
如果需要 JsonFormat 这类 JSON 转换、well-known types 处理等特性,还需额外添加 protobuf-java-util:
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version><!--version--></version>
</dependency>
该包对应仓库中的 java/util/ 目录,其源码与 proto 文件(如 duration.proto、timestamp.proto 等扩展定义)均位于其中。
三、Android 场景:Java Lite 运行时
文档明确建议:Android 用户应使用 protobuf Java Lite 运行时。理由有三:
- 更小的代码体积:Lite 是专为移动端设计的独立运行时,设计目标就是小二进制体积和更低的峰值内存;
- 与 ProGuard/R8 更友好:它不依赖 Java 反射,代码被裁剪(stripping)的能力更强;
- API 是完整运行时的子集:从 examples/BUILD.bazel 的注释可以看到,由于 Lite API 是常规 Java API 的子集,只要你只用这个子集,代码可以同时编译进服务端构建和 Android 构建,实现双端共享。
Lite 运行时的取舍细节记录在 java/lite.md 中,值得完整了解:
- 功能子集:无反射 API、无 ProtoJSON、无 TextFormat 支持;
- 运行时性能特征可能慢于完整运行时;
- 在 API/ABI 稳定性上不做保证(为性能和体积让路);
- 它依赖
sun.misc.Unsafe来优化性能,因此在sun.misc.Unsafe不可用的 JVM 环境中无法使用 Lite 运行时(这也意味着它不适合在标准 JVM 服务端使用——文档明确 Lite 不应用于服务端,服务端应始终用完整运行时)。
3.1 生成 Lite 代码与引入依赖
生成 Lite 版本的 Java 代码时,在输出参数前加上 lite: 前缀:
$ protoc --java_out=lite:${OUTPUT_DIR} path/to/your/proto/file
Maven 依赖改为:
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-javalite</artifactId>
<version><!--version--></version>
</dependency>
在 Bazel 中这一行为同样内建:java/lite/BUILD.bazel 里定义的 proto_lang_toolchain 目标,其编译命令行正是 --java_out=lite:$(OUT),运行时指向 :lite 目标,并对 well-known protos(any、api、duration、struct 等)做了黑名单排除——因为 Lite 运行时不支持这些类型。
3.2 R8/ProGuard 保活规则
Lite 运行时内部使用反射来避免为每个消息生成 hashCode/equals/parse/serialize 方法(从 GeneratedMessageLite.java 对 com.google.protobuf.Internal 包的引用可以看到这种"运行时查找"式设计)。而 R8 默认会混淆字段名,导致反射失败,抛出形如 java.lang.RuntimeException: Field {NAME}_ for {CLASS} not found 的异常(源头在 MessageSchema.java)。
java/lite.md 给出的缓解方案是在 proguard-rules.pro 中添加:
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
保留所有 GeneratedMessageLite 子类的字段名,即可避免混淆破坏反射路径。
四、Bazel 集成
Bazel 有原生的 protobuf 构建规则,Java 场景使用:
java_proto_library:服务端,基于完整运行时;java_lite_proto_library:Android/移动端,基于 Lite 运行时。
仓库的 examples/BUILD.bazel 是官方给出的完整示例,核心模式是"一个语言无关的 proto_library + 每个语言一个生成目标":
# proto_library 不绑定具体语言,只描述 .proto 依赖图,
# 并向编译器提供 .proto 源文件。
proto_library(
name = "addressbook_proto",
srcs = ["addressbook.proto"],
deps = ["@com_google_protobuf//:timestamp_proto"],
)
# Java 完整运行时(服务端)
java_proto_library(
name = "addressbook_java_proto",
deps = [":addressbook_proto"],
)
# Java Lite(Android/移动端)
java_lite_proto_library(
name = "addressbook_java_lite_proto",
deps = [":addressbook_proto"],
)
java_binary 再依赖对应的 java_proto_library/java_lite_proto_library 目标即可,例如 add_person_java 依赖 :addressbook_java_proto 和 @com_google_protobuf//java/util。
关于包体积收益,examples/BUILD.bazel 中的注释给出了可复现的对比方式:
$ bazel build :add_person_java_deploy.jar :add_person_java_lite_deploy.jar
$ ls -l bazel-bin/*_deploy.jar
# add_person_java_deploy.jar 1230797 字节
# add_person_java_lite_deploy.jar 236166 字节
示例中 Lite 版 jar 比完整版小约 6 倍;若再配合 ProGuard 的 inlining/stripping,差距会更大。规则的实现位于 bazel/java_proto_library.bzl 与 bazel/java_lite_proto_library.bzl。
五、从源码构建 Java 运行时
大多数用户直接按上述方式使用已发布的运行时即可。但如果你是 protobuf 贡献者,或想使用尚未正式发布的版本,可以从源码构建。注意该流程不需要安装 Maven,会跳过单元测试,且只安装核心库(不含 util 包):
-
构建 C++ 代码或获取 protoc 二进制发行版(参考顶层 README.md)。若安装二进制发行版,务必确认其版本与 Java 包版本一致,可运行
protoc --version检查。若从源码构建了 C++ 代码但未安装,编译器二进制位于../src目录下; -
调用 protoc 生成 DescriptorProtos.java:
$ protoc --java_out=core/src/main/java -I../src \ ../src/google/protobuf/descriptor.proto -
用任意方式编译
core/src/main/java下的代码; -
将编译产物安装到你希望的位置。
第 2 步的原理在于:Java 运行时的描述符支持代码本身就是由 descriptor.proto 生成的,所以自举构建的第一步就是用 protoc 把描述符协议编译成 Java。
六、Kotlin Protocol Buffers
java/ 目录同时提供 Kotlin 支持。Kotlin protobuf 构建在 Java protobuf 之上,有两个硬约束:
- 必须依赖 Java protobuf 运行时;
- 每个 .proto 文件必须同时生成 Java 和 Kotlin 代码。
生成命令是两个 --*_out 并用:
$ protoc --java_out=${OUTPUT_DIR} --kotlin_out=${OUTPUT_DIR} path/to/your/proto/file
Kotlin protobuf 的目标是为 Kotlin 提供符合语言习惯(idiomatic)的构建与读取方式(如 DSL 风格的构建器、不可变集合扩展等)。
6.1 Maven 依赖
Kotlin 场景需要同时引入 Java 与 Kotlin 两个运行时,且版本相互一致、并匹配(或不旧于)protoc:
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version><!--version--></version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-kotlin</artifactId>
<version><!--version--></version>
</dependency>
6.2 Kotlin Lite(Android)
Android 上推荐继续使用 Java Lite 运行时。仓库为此提供了 protobuf-kotlin-lite 包(Maven 与 Bazel 均有),与 Java Lite 运行时配对使用。从 java/kotlin-lite/pom_template.xml 可以看到它的依赖声明:
<dependencies>
<dependency>
<groupId>{groupId}</groupId>
<artifactId>protobuf-javalite</artifactId>
<version>{version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
即 protobuf-kotlin-lite 本身也要求依赖 protobuf-javalite。
6.3 仓库中的 Kotlin 源码结构
从 java/kotlin/BUILD.bazel 可以看出 Kotlin 运行时的组织方式:
shared_runtime目标(注释明确"Kotlin generated protos depend on this and only this")包含生成代码直接依赖的 DSL 基础设施:DslList.kt、DslMap.kt、DslProxy.kt、ExtensionList.kt、UnmodifiableCollections.kt;full_extensions目标(Anies.kt、ExtendableMessageExtensions.kt)依赖完整//java/core运行时,提供扩展与Any支持——这解释了为何 Kotlin 生成代码"同时需要 Java + Kotlin 两套代码";- 测试侧通过
internal_gen_kt_protos规则对测试 proto 做双语言生成(//src/google/protobuf:kt_unittest_protos等),并有 Proto2Test.kt、Proto3Test.kt 等测试覆盖。
七、兼容性须知(必须遵守的七条规则)
java/README.md 的 "Compatibility Notice" 是升级运行时前必读的内容,逐条继承如下:
- 次版本(minor)发布向后兼容:如果你的代码能在旧版本上构建/运行,按照本节指南操作,就应能在新版本上构建/运行。次版本发布同时保证二进制兼容与源码兼容;
- 主版本(major)发布可能向后兼容上一个主版本的最后一次发布,具体见该次发布的发布说明;
@ExperimentalApi标注的 API 可随时变更,包括任意修改甚至直接删除。如果项目需要兼容性就不要使用它们;如果你的代码本身是库(出现在你无法控制的下游用户的 CLASSPATH 上),更不应使用实验性 API,除非你重新打包(例如用 ProGuard 混淆包名);- 已弃用的非实验 API 会在首次弃用发布两年后移除。你必须在期限内修复引用,否则后果不可预测(不保证会给出编译错误);
- 不要自行继承消息接口/类。Protobuf 消息接口/类设计为仅由生成的 protobuf 代码继承,后续版本可能新增方法从而破坏你的自定义子类;
- 不要使用任何标注"used by generated code only"的方法/类,它们随时可能变更。仓库中这类 API 的典型代表就是 Internal.java 所在包的内部工具类——例如
GeneratedMessageLite就大量引用com.google.protobuf.Internal.*(IntList、BooleanList等); - LITE 运行时 API 尚不稳定,即使在次版本发布中也可能变更。
八、延伸阅读
- 完整官方文档位于 Protocol Buffers 开发者文档站点(仓库文档指向的权威入口);
- 仓库内相关入口:java/lite.md(Lite 运行时取舍与 R8 规则细节)、examples/BUILD.bazel(多语言 Bazel 集成示例)、java/pom.xml(Maven 多模块与依赖管理)、bazel/java_lite_proto_library.bzl(Lite 生成规则实现)。
小结:Java 侧的 protobuf 使用路径可以归纳为一句话——服务端用 protoc --java_out + protobuf-java(需要 JSON 能力再加 protobuf-java-util),移动端用 --java_out=lite: + protobuf-javalite(并配置 R8 保活规则),Kotlin 则在两者之上叠加 protobuf-kotlin/protobuf-kotlin-lite 并保持每个 proto 双语言生成;同时严格遵守运行时版本不低于 protoc 以及第七节的兼容性规则,即可在任意版本升级中保持构建稳定。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00