这篇指南将涵盖从零开始的完整流程,包括:

- 环境准备:安装和配置 Protobuf 编译器 (
protoc) 和 Java Maven 依赖。 - 定义
.proto文件:创建你的数据结构定义。 - 生成 Java 代码:使用
protoc编译器生成 Java 类。 - 序列化:将 Java 对象转换为字节数组。
- 反序列化:将字节数组还原为 Java 对象。
- 进阶用法:如处理嵌套对象、集合、枚举等。
环境准备
在开始之前,你需要确保你的开发环境已经配置好 Protobuf。
a. 安装 Protobuf 编译器 (protoc)
你需要从 Protocol Buffers 的官方 GitHub 发布页面 下载适合你操作系统的 protoc。
以 macOS 为例 (使用 Homebrew):
brew install protobuf
验证安装:

protoc --version # 应该输出类似 libprotoc 3.21.12 的信息
b. 配置 Java Maven 项目
在你的 pom.xml 文件中,添加 protobuf-java 和 protobuf-java-util 依赖。protobuf-java 是核心库,而 protobuf-java-util 提供了一些有用的工具,JsonFormat。
<dependencies>
<!-- Protocol Buffers Core Library -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.24.4</version> <!-- 使用最新的稳定版本 -->
</dependency>
<!-- Optional: For JSON support and other utilities -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>3.24.4</version>
</dependency>
</dependencies>
定义 .proto 文件
这是整个流程的起点,你需要创建一个 .proto 文件来定义你的数据结构,我们创建一个名为 user.proto 的文件。
src/main/resources/user.proto
// 指定使用的 proto 版本
syntax = "proto3";
// 指定生成的 Java 类的包名
option java_package = "com.example.models";
// 指定生成的外部类名,如果不指定,默认是文件名(首字母大写)
option java_outer_classname = "UserProto";
// 定义一个消息,类似于 Java 中的类
message User {
// 字段类型 字段名 = 字段编号;
// 字段编号是唯一的,在修改 .proto 文件时不要轻易改动
int32 id = 1;
string name = 2;
string email = 3;
// 嵌套的消息
message Address {
string street = 1;
string city = 2;
int32 zip_code = 3;
}
// 可选的嵌套对象
optional Address address = 4; // 在 proto3 中,optional 已被废弃,推荐使用 oneof 或包装类型
// 使用 oneof 来确保只有一个字段被设置
oneof contact_info {
string phone_number = 5;
string fax_number = 6;
}
}
关键字说明:

syntax = "proto3": 使用 Protocol Buffers 的第 3 版语法。message: 定义一个结构,类似于 Java 的 class 或 POJO。int32,string: 数据类型,会自动映射到 Java 的int,String。= 1, = 2: 字段编号,这是 Protobuf 的核心,用于标识字段,序列化时只存储字段编号和值,不存储字段名,所以非常高效。一旦定义,就不要修改,否则会破坏兼容性。optional: 在 proto2 中表示字段可以不存在,在 proto3 中,所有基本类型字段都是可选的(默认为0或空字符串),但optional关键字已被废弃,对于需要明确“未设置”语义的字段(如int32),推荐使用google.protobuf.Int32Value这样的包装类型。oneof: 确保一组字段中只有一个被设置。contact_info要么是phone_number,要么是fax_number,但不能同时是两者。option java_package: 生成的 Java 类的包。option java_outer_classname: 生成的最外层 Java 类的类名,这个类将包含所有内部消息类(如User,Address)的静态类。
生成 Java 代码
你需要使用 protoc 编译器来根据 .proto 文件生成 Java 代码。
在项目根目录下执行以下命令:
# -I: 指定 .proto 文件所在的目录 # --java_out: 指定 Java 代码的输出目录 # 最后是 .proto 文件的路径 protoc -I=src/main/resources --java_out=src/main/java src/main/resources/user.proto
执行成功后,你会在 src/main/java/com/example/models/ 目录下找到一个名为 UserProto.java 的文件。这个文件是自动生成的,你绝对不应该手动编辑它!
打开 UserProto.java,你会看到类似这样的结构:
- 一个
public final class UserProto。 - 内部有
public static final class User extends GeneratedMessageV3类,这就是你用来操作的对象。 - 内部有
public static final class Address extends GeneratedMessageV3类。 - 各种
Builder类,用于构建User对象。
序列化 - 将 Java 对象转为字节数组
序列化的过程就是创建一个 User 对象,然后调用它的 toByteArray() 方法。
import com.example.models.UserProto;
import java.io.IOException;
public class SerializationExample {
public static void main(String[] args) {
// 1. 创建一个 User 对象(通过 Builder 模式)
UserProto.User user = UserProto.User.newBuilder()
.setId(101)
.setName("Alice")
.setEmail("alice@example.com")
.setAddress(UserProto.User.Address.newBuilder()
.setStreet("123 Main St")
.setCity("Wonderland")
.setZipCode(12345)
.build())
.setPhoneNumber("123-456-7890") // 设置 oneof 字段
.build();
// 2. 序列化为字节数组
byte[] serializedBytes = user.toByteArray();
System.out.println("序列化成功!字节数组长度: " + serializedBytes.length);
// 你可以将这个字节数组写入文件、通过网络发送等
// Files.write(Paths.get("user.dat"), serializedBytes);
}
}
反序列化 - 将字节数组还原为 Java 对象
反序列化的过程就是调用 UserProto.User.parseFrom() 方法,并传入之前生成的字节数组。
import com.example.models.UserProto;
import java.io.IOException;
public class DeserializationExample {
public static void main(String[] args) throws IOException {
// 假设这是从某个地方接收到的字节数组
byte[] serializedBytes = getSerializedBytes(); // 我们复用上面的序列化结果
// 1. 从字节数组反序列化
UserProto.User deserializedUser = UserProto.User.parseFrom(serializedBytes);
// 2. 访问反序列化后的对象数据
System.out.println("反序列化成功!");
System.out.println("ID: " + deserializedUser.getId());
System.out.println("Name: " + deserializedUser.getName());
System.out.println("Email: " + deserializedUser.getEmail());
if (deserializedUser.hasAddress()) {
System.out.println("Address: " + deserializedUser.getAddress().getStreet() + ", " + deserializedUser.getAddress().getCity());
}
if (deserializedUser.getPhoneNumber().isEmpty()) {
System.out.println("Phone Number: " + deserializedUser.getFaxNumber());
} else {
System.out.println("Phone Number: " + deserializedUser.getPhoneNumber());
}
}
// 模拟获取字节数组
private static byte[] getSerializedBytes() {
UserProto.User user = UserProto.User.newBuilder()
.setId(101)
.setName("Alice")
.setEmail("alice@example.com")
.setAddress(UserProto.User.Address.newBuilder()
.setStreet("123 Main St")
.setCity("Wonderland")
.setZipCode(12345)
.build())
.setPhoneNumber("123-456-7890")
.build();
return user.toByteArray();
}
}
进阶用法
a. 处理集合 (Lists)
Protobuf 中,repeated 关键字用来定义数组或列表。
在 user.proto 中添加:
repeated string hobbies = 7;
在 Java 代码中使用:
// 序列化时
UserProto.User user = UserProto.User.newBuilder()
// ... 其他字段
.addHobby("Reading")
.addHobby("Hiking")
.addHobby("Coding")
.build();
// 反序列化后访问
for (String hobby : deserializedUser.getHobbiesList()) {
System.out.println("Hobby: " + hobby);
}
b. 处理枚举
在 .proto 文件中定义枚举。
在 user.proto 中添加:
enum Gender {
GENDER_UNSPECIFIED = 0;
MALE = 1;
FEMALE = 2;
OTHER = 3;
}
message User {
// ... 其他字段
Gender gender = 8;
}
在 Java 代码中使用:
// 序列化时
UserProto.User user = UserProto.User.newBuilder()
// ... 其他字段
.setGender(UserProto.Gender.MALE)
.build();
// 反序列化后访问
UserProto.Gender gender = deserializedUser.getGender();
System.out.println("Gender: " + gender); // 输出 MALE
c. JSON 格式转换 (protobuf-java-util)
利用 JsonFormat 可以轻松地在 Protobuf 消息和 JSON 字符串之间转换。
import com.google.protobuf.util.JsonFormat;
import com.example.models.UserProto;
public class JsonFormatExample {
public static void main(String[] args) throws Exception {
// 1. 创建一个 User 对象
UserProto.User user = UserProto.User.newBuilder()
.setId(101)
.setName("Bob")
.setEmail("bob@example.com")
.build();
// 2. 将 Protobuf 消息转换为 JSON 字符串
String jsonString = JsonFormat.printer().omittingInsignificantWhitespace().print(user);
System.out.println("JSON 格式:");
System.out.println(jsonString);
// 输出: {"id":101,"name":"Bob","email":"bob@example.com"}
// 3. 将 JSON 字符串解析回 Protobuf 消息
UserProto.User.Builder userBuilder = UserProto.User.newBuilder();
JsonFormat.parser().merge(jsonString, userBuilder);
UserProto.User userFromJson = userBuilder.build();
System.out.println("\n从 JSON 解析后的 ID: " + userFromJson.getId());
}
}
| 操作 | 核心方法 | 描述 |
|---|---|---|
| 序列化 | message.toByteArray() |
将 GeneratedMessageV3 对象转换为 byte[]。 |
| 反序列化 | Message.parseFrom(byte[]) |
从 byte[] 创建 GeneratedMessageV3 对象。 |
| 构建对象 | Message.newBuilder() |
使用 Builder 模式创建和修改消息对象。 |
| JSON 转换 | JsonFormat.printer() / JsonFormat.parser() |
在 Protobuf 消息和 JSON 字符串之间转换。 |
Protobuf 的核心优势在于其高效的二进制格式、强类型定义以及跨语言的兼容性,遵循上述步骤,你就可以在 Java 项目中顺利地使用 Protobuf 进行数据序列化和反序列化了。
