Spring boot 2.5 集成 Google protocol buffer
1 摘要
Portocol Buffers 是一种由 Google 开源的数据序列化格式,简称 protobuf,这是一种支持多语言、无关操作系统平台的、具有拓展性的数据格式。为什么要有 protobuf?或者说 protobuf 的应用场景有哪些?答案是 probuf 的最大优势是体积小,适用于网络状况不佳或者网络带宽有限的设备之间通讯,比如 IoT 之间数据传输。对于网页而言,则完全没有必要。除了网络带宽足够外,protobuf 在编码方面也有缺陷,因为虽然 protobuf 是支持多语言,但是数据转换确实一件比较麻烦的事。以 Java 为例,protobuf 支持生成 Java 类对象,但是却并没有 setter/getter 方法,属性赋值是通过 Builder 方式,无法使用常规的 BeanUtil 进行对象间的属性转换;生成的代码可读性也很差,想要看清楚类定义的属性,基本上还是要看原本的 .proto
文件;不能对字段添加注解等等。所以, protobuf 的使用场景有限,并不能代替 Java 的 POJO。本文将介绍基于 Spring boot 2.5 集成 protobuf。
Protobuf 官方文档:
https://developers.google.com/protocol-buffers
https://developers.google.com/protocol-buffers/docs/overview
2 核心 Maven 依赖
./demo-mybatis-plus/pom.xml
<!-- protocol buffer support -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-all</artifactId>
<version>${grpc.version}</version>
</dependency>
其中 ${protobuf.version}
的版本为 3.17.3
,${grpc.version}
的版本为 1.39.0
插件依赖
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.0</version>
</extension>
</extensions>
<plugins>
<!-- protocol buffer -->
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protoSourceRoot>${basedir}/src/main/java</protoSourceRoot>
<outputDirectory>${basedir}/src/main/java</outputDirectory>
<protocArtifact>
com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>
io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
</pluginArtifact>
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
其中 os-maven-plugin
插件是用于获取操作系统信息的
protobuf-maven-plugin
是 protobuf
打包插件
插件配置说明:
protoSourceRoot
用于配置 protobuf 源文件的路径,可以具体到包名,也可以直接指向 Java 包根目录(建议)
outputDirectory
用于配置 protobuf 生成 Java 类文件的路径,可以具体到包名,也可以直接指向 Java 包根目录(建议)
clearOutputDirectory
是否清理输出目录,默认为 true,即在打包的时候,会将 protobuf 生成 Java 类文件的目录给清空,如果该目录下有其他类文件,也会一并清除,建议设置为 false
以上配置方式是无关操作系统的,即可以在任何操作系统下只要将代码下载下来,就可执行,不需要单独配置一个 protoc.exe
文件的目录
3 IDE protobuf 插件安装
protobuf 并非 Java 自带语言格式,编辑 protobuf 文件无法格式化,需要借助插件才能实现,以 Intellij IDEA 为例,需要使用的插件名称为 Protobuf
IDEA 安装插件步骤:Settings -> Plugins -> 搜索「protobuf」 -> 选择 「Protobuf」进行安装
插件安装之后需要重启 IDEA
4 编辑 protobuf 文件
4.1 创建 protobuf 文件
protobuf 有两个版本,分别为 proto2 和 proto3,两者之间的语法差别很大,建议使用新版 proto3
Protobuf 的 proto3 与 proto2 的区别
protobuf3 官方文档: Language Guide (proto3)
在 IDEA 中新建 protobuf 文件:
选择目录 -> 鼠标右键,选择「New」 -> 选择「File」-> 文件名为「xxx.proto」
./demo-mybatis-plus/src/main/java/com/ljq/demo/springboot/mybatisplus/model/protobuf/Student.proto
syntax = "proto3";
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 birthDate = 3;
}
// 班级信息
message ClassInfo {
// id
int64 id = 1;
// 年级
int32 grade = 2;
// 班级编号
int32 number = 3;
}
protobuf 文件主要参数说明:
syntax
: 用于定义 protobuf 版本,目前有 proto2
和 proto3
可选,建议使用 proto3
java_package
: 用于定义生成 Java 类的包名
java_outer_classname
: 用于定义生成的 Java 类名。该类名不能与后边定义的 message
名称一样,因为每一个 message
都会生成一个 Java 类
4.2 生成 Java 类
在项目目录下执行 Maven 打包命令
maven clean package -D maven.test.skip=true
打包命令执行完成之后,即可生成对应的 Java 类
生成的 Java 类较大,这里贴出部分源码
./demo-mybatis-plus/src/main/java/com/ljq/demo/springboot/mybatisplus/model/entity/StudentPb.java
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: com/ljq/demo/springboot/mybatisplus/model/protobuf/Student.proto
package com.ljq.demo.springboot.mybatisplus.model.entity;
public final class StudentPb {
private StudentPb() {}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistryLite registry) {
}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistry registry) {
registerAllExtensions(
(com.google.protobuf.ExtensionRegistryLite) registry);
}
public interface StudentOrBuilder extends
// @@protoc_insertion_point(interface_extends:Student)
com.google.protobuf.MessageOrBuilder {
/**
* <pre>
* id
* </pre>
*
* <code>int64 id = 1;</code>
* @return The id.
*/
long getId();
/**
* <pre>
* 姓名
* </pre>
*
* <code>string name = 2;</code>
* @return The name.
*/
java.lang.String getName();
/**
* <pre>
* 姓名
* </pre>
*
* <code>string name = 2;</code>
* @return The bytes for name.
*/
com.google.protobuf.ByteString
getNameBytes();
/**
* <pre>
* 出生日期(时间戳)
* </pre>
*
* <code>int32 birthDate = 3;</code>
* @return The birthDate.
*/
int getBirthDate();
}
/**
* <pre>
* 学生信息
* </pre>
*
* Protobuf type {@code Student}
*/
public static final class Student extends
com.google.protobuf.GeneratedMessageV3 implements
// @@protoc_insertion_point(message_implements:Student)
StudentOrBuilder {
private static final long serialVersionUID = 0L;
// Use Student.newBuilder() to construct.
private Student(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
super(builder);
}
private Student() {
name_ = "";
}
... ...
5 使用示例
5.1 参数接收类
定义一个参数接收类,用于接口参数接收以及 protobuf 数据读取
./demo-mybatis-plus/src/main/java/com/ljq/demo/springboot/mybatisplus/model/param/student/StudentReceiveParam.java
package com.ljq.demo.springboot.mybatisplus.model.param.student;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @Description: 接收学生信息(pb)
* @Author: junqiang.lu
* @Date: 2021/8/7
*/
@Data
@ApiModel(value = "接收学生信息(pb)", description = "查询学生信息")
public class StudentReceiveParam implements Serializable {
private static final long serialVersionUID = -8856061586998092638L;
/**
* id
*/
@ApiModelProperty(value = "id", required = true)
private Long id;
/**
* 姓名
*/
@ApiModelProperty(value = "姓名", required = true)
private String name;
/**
* 出生日期(时间戳,精确到秒)
*/
@ApiModelProperty(value = "出生日期(时间戳,精确到秒)", required = true)
private Integer birthDate;
}
5.2 protobuf 的赋值与取值
./demo-mybatis-plus/src/main/java/com/ljq/demo/springboot/mybatisplus/controller/StudentController.java
package com.ljq.demo.springboot.mybatisplus.controller;
import com.ljq.demo.springboot.mybatisplus.common.api.ApiResult;
import com.ljq.demo.springboot.mybatisplus.model.entity.StudentPb;
import com.ljq.demo.springboot.mybatisplus.model.param.student.StudentReceiveParam;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StreamUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @Description: 学生信息控制层
* @Author: junqiang.lu
* @Date: 2021/8/8
*/
@Slf4j
@RestController
@RequestMapping(value = "/api/protocol/buffer/student")
@Api(value = "protobuf-学生信息控制层", tags = "protobuf-学生信息控制层")
public class StudentController {
/**
* 发送学生信息
*
* @param request
* @return
*/
@PostMapping(value = "/send", produces = {MediaType.APPLICATION_JSON_VALUE})
@ApiOperation(value = "发送学生信息(pb)", notes = "发送学生信息(pb)")
public ResponseEntity<?> send(HttpServletRequest request) throws Exception{
// 读取字节流
byte[] studentBytes = StreamUtils.copyToByteArray(request.getInputStream());
// protocol buffer 反序列化
StudentPb.Student student = StudentPb.Student.parseFrom(studentBytes);
// 读取属性
log.info("Student info,id: {}, name: {}, birthDate: {}", student.getId(), student.getName(),
student.getBirthDate());
/**
* 业务处理
* ... ...
*/
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>(ApiResult.success(), headers, HttpStatus.OK);
}
/**
* 接收学生信息(pb)
*
* @param receiveParam
* @return
*/
@GetMapping(value = "/receive", produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
@ApiOperation(value = "接收学生信息(pb)", notes = "接收学生信息(pb)")
public ResponseEntity<?> receive(@Validated StudentReceiveParam receiveParam) throws Exception {
/**
* 业务处理
* ... ...
*/
// 赋值
StudentPb.Student student = StudentPb.Student.newBuilder()
.setId(receiveParam.getId())
.setName(receiveParam.getName())
.setBirthDate(receiveParam.getBirthDate())
.build();
// 封装成 byte 数组
byte[] studentBytes = student.toByteArray();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return new ResponseEntity<>(studentBytes, headers, HttpStatus.OK);
}
}
6 测试
6.1 序列化
6.2 反序列化
日志:
2021-08-13 15:03:02.160 INFO 20632 --- [nio-8450-exec-4] c.l.d.s.m.controller.StudentController : Student info,id: 123, name: 德玛西亚, birthDate: 1628233076
至此,Spring boot 与 protobuf 的简易集成已经完成。
7 推荐参考资料
官方文档 https://developers.google.com/protocol-buffers
官方文档 https://developers.google.com/protocol-buffers/docs/overview
官方文档 Protocol Buffer Basics: Java
8 Github 源码
Gtihub 源码地址 : https://github.com/Flying9001/springBootDemo
个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.