1 摘要
什么是 protobuf? Java Spring boot 如何集成 protobuf? 具体可参考以下博客:
Spring boot 2.5 集成 Google protocol buffer
Protobuf 官方文档:
https://developers.google.com/protocol-buffers
https://developers.google.com/protocol-buffers/docs/overview
本文将介绍如何定义较为复杂格式的 protobuf 文件,包括对象字段,列表字段,枚举类等
2 核心代码
2.1 protobuf 文件
./demo-mybatis-plus/src/main/java/com/ljq/demo/springboot/mybatisplus/model/protobuf/Student.proto
syntax = "proto3";
option optimize_for = CODE_SIZE;
option java_package = "com.ljq.demo.springboot.mybatisplus.model.entity";
option java_outer_classname = "StudentPb";
// 学生信息
message Student {
// id
int64 id = 1;
// 姓名
string name = 2;
// 出生日期(时间戳)
int32 birth_date = 3;
}
// 班级信息
message ClassInfo {
// id
int64 id = 1;
// 年级
int32 grade = 2;
// 班级编号
int32 number = 3;
}
// 学生选修课
message ElectiveCourse {
// 学生 id
int64 stu_id = 1;
// 课程名称
repeated string course_names = 2;
}
// 学生班级信息
message StudentClass {
// id
int64 id = 1;
// 学生信息
Student student = 2;
// 班级信息
ClassInfo class_info = 3;
}
// 性别
message StudentSex {
enum Sex{
MAN = 0;
WOMEN = 1;
}
Sex sex = 2;
}
// 教师信息
message Teacher {
// id
uint64 id = 1;
// 姓名
string name = 2;
}
简要说明:
ElectiveCourse
选修课 message 中「课程名称」是属于列表形式的
StudentClass
学生班级信息中包含 Student
和 ClassInfo
对象
StudentSex
学生性别是枚举类的示例,枚举类元素与其他 message 元素定义的区别在于枚举类的元素下标是从 0 开始的。
proto 文件定义好之后需要编译,然后才能生成对应的 Java 类
2.2 测试示例
在生成 Java 类之后进行测试
./demo-mybatis-plus/src/test/java/com/ljq/demo/springboot/mybatisplus/model/entity/StudentPbTest.java
package com.ljq.demo.springboot.mybatisplus.model.entity;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.ljq.demo.springboot.mybatisplus.model.param.student.StudentReceiveParam;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
public class StudentPbTest {
/**
* 测试-赋值与取值
*/
@Test
public void value() {
// 赋值
StudentPb.Student student = StudentPb.Student.newBuilder()
.setId(111L)
.setName("张三")
.setBirthDate(1628233076)
.build();
System.out.println(student.toString());
// 测试结果:
// id: 111
// name: "\345\274\240\344\270\211"
// birth_date: 1628233076
// 测试结论: 字符串的 toString 为编码
// 取值
System.out.println("id: " + student.getId());
System.out.println("name: " + student.getName());
System.out.println("birth_date: " + student.getBirthDate());
// 测试结果:
// id: 111
// name: 张三
// birth_date: 1628233076
// 测试结论: 逐个属性取值不存在编码问题
// BeanUtil 属性赋值测试
StudentReceiveParam receiveParam = new StudentReceiveParam();
BeanUtil.copyProperties(student, receiveParam, CopyOptions.create().ignoreError().ignoreNullValue());
System.out.println(receiveParam);
// 测试结果:
// StudentReceiveParam(id=null, name=null, birth_date=null)
// 测试结论: Protobuf 生成的 java 类无法使用 beanUtil 进行属性复制
}
/**
* 测试-数据转换
*/
@Test
public void convert() throws IOException {
StudentPb.Student student = StudentPb.Student.newBuilder()
.setId(111L)
.setName("张三")
.setBirthDate(1628233076).build();
// 转化为字节数组
byte[] bytes = student.toByteArray();
// 写入本地
String localPath = "/Users/ljq/Downloads/student.bin";
Files.write(Paths.get(localPath), bytes);
// 从流中读取数据
byte[] readBytes = Files.readAllBytes(Paths.get(localPath));
StudentPb.Student readStudent = StudentPb.Student.parseFrom(readBytes);
// 取值
System.out.println("id: " + readStudent.getId());
System.out.println("name: " + readStudent.getName());
System.out.println("birth_date: " + readStudent.getBirthDate());
}
/**
* 测试-列表字段
*/
@Test
public void list() {
// 赋值
StudentPb.ElectiveCourse electiveCourse = StudentPb.ElectiveCourse.newBuilder()
.setStuId(6L)
.addCourseNames("物理")
.addCourseNames("化学")
.addCourseNames("生物")
.build();
// 取值
System.out.println("Student id: " + electiveCourse.getStuId());
List<String> courseNameList = electiveCourse.getCourseNamesList();
System.out.println("elective course: ");
courseNameList.stream().forEach(s -> System.out.println(s));
}
/**
* 测试-字段为对象
*/
@Test
public void object() {
// 赋值
StudentPb.StudentClass studentClass = StudentPb.StudentClass.newBuilder()
.setId(7L)
.setStudent(StudentPb.Student.newBuilder()
.setId(123L)
.setName("德玛西亚")
.setBirthDate(1628233076).build())
.setClassInfo(StudentPb.ClassInfo.newBuilder()
.setId(456L)
.setGrade(3)
.setNumber(2).build())
.build();
// 取值
System.out.println("id: " + studentClass.getId());
// 学生信息
System.out.println("student id: " + studentClass.getStudent().getId());
System.out.println("student name: " + studentClass.getStudent().getName());
System.out.println("student birth_date: " + studentClass.getStudent().getBirthDate());
// 班级信息
System.out.println("class id: " + studentClass.getClassInfo().getId());
System.out.println("class grade: " + studentClass.getClassInfo().getGrade());
System.out.println("class number: " + studentClass.getClassInfo().getNumber());
}
/**
* 测试-枚举类
*/
@Test
public void enums() {
StudentPb.StudentSex studentSex = StudentPb.StudentSex.newBuilder()
.setSex(StudentPb.StudentSex.Sex.MAN)
.build();
// 取值
System.out.println("student sex: " + studentSex.getSex());
System.out.println("student sex value: " + studentSex.getSexValue());
// 测试结果
// student sex: MAN
// student sex value: 0
// 测试结论: 直接获取枚举类,返回的是枚举对象/字符串;获取枚举类的值,返回的是枚举类中对应的属性值
// protobuf 在生成枚举类的时候会默认给每一个枚举值赋值为整数,从 0 开始
}
}
3 推荐参考资料
官方文档 https://developers.google.com/protocol-buffers
官方文档 https://developers.google.com/protocol-buffers/docs/overview
官方文档 Protocol Buffer Basics: Java
4 Github 源码
Gtihub 源码地址 : https://github.com/Flying9001/springBootDemo
个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.