Spring boot 2.5 集成 Google protocol buffer

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-pluginprotobuf 打包插件

插件配置说明:

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

Protobuf-plugin

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 版本,目前有 proto2proto3 可选,建议使用 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 序列化

protobuf-序列化接口测试

6.2 反序列化

protobuf-反序列化

日志:

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

maven 配置protocol buffer

8 Github 源码

Gtihub 源码地址 : https://github.com/Flying9001/springBootDemo

个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.
404Code

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值