【SpringBoot3】结合 gRpc 通过 proto文件生成Java代码

一、什么是protobuf

Protobuf(Protocol Buffers)是由Google开发的一种灵活、高效、自动化的结构化数据序列化方法,类似于XML、JSON等,但更小、更快、更简单。它主要用于网络通信和数据存储等场景,广泛应用于各种编程语言中。

Protobuf的作用主要体现在以下几个方面:

  1. 数据序列化与反序列化

    Protobuf可以将数据结构或对象序列化为一个二进制流,同时也可以将这个二进制流反序列化回原始数据结构。这种序列化方式相比于传统的文本格式(如XML、JSON)更加高效,因为它采用了二进制格式,减少了存储空间和网络传输的带宽消耗。

  2. 跨语言通信

    Protobuf支持多种编程语言,包括C++、Java、Python等。这意味着在不同编程语言编写的程序之间,可以使用Protobuf格式进行高效的数据交换和通信。

  3. 向后兼容性

    Protobuf设计时就考虑到了向后兼容性。当数据结构发生变化时(例如添加新的字段),旧的程序仍然可以读取由新程序序列化的数据,忽略新增的字段。

  4. 自动化处理

    Protobuf编译器(protoc)可以根据.proto文件(定义数据结构的文本文件)自动生成对应语言的源代码,这些源代码提供了序列化、反序列化以及数据访问的方法,极大地简化了开发过程。

二、基本使用步骤

1、在pom.xml中添加编译插件和相关依赖

xml 复制代码
<properties>
<!--<os.detected.classifier>windows-x86_64</os.detected.classifier>-->
    <protobuf-maven-plugin.version>0.6.1</protobuf-maven-plugin.version>
    <protoc.version>3.25.3</protoc.version>
    <protobuf.java.version>3.25.3</protobuf.java.version>
    <grpc-java.version>1.64.0</grpc-java.version>
</properties>

//...//
<dependencies>
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java</artifactId>
        <version>${protobuf.java.version}</version>
    </dependency>
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java-util</artifactId>
        <version>${protobuf.java.version}</version>
    </dependency>
</dependencies>
// ... //

<build>
    <pluginManagement>
        <plugins>
            <!-- protobuf-maven-plugin -->
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>${protobuf-maven-plugin.version}</version>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <id>protoc-compile</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>protoc-test-compile</id>
                        <phase>generate-test-sources</phase>
                        <goals>
                            <goal>test-compile</goal>
                            <goal>test-compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <protocArtifact>
                        com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}
                    </protocArtifact>
                    <attachProtoSources>true</attachProtoSources>
                    <useArgumentFile>true</useArgumentFile>
                    <writeDescriptorSet>false</writeDescriptorSet>
                    <attachDescriptorSet>false</attachDescriptorSet>
                    <includeDependenciesInDescriptorSet>false</includeDependenciesInDescriptorSet>
                    <checkStaleness>true</checkStaleness>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>
                        io.grpc:protoc-gen-grpc-java:${grpc-java.version}:exe:${os.detected.classifier}
                    </pluginArtifact>
                    <protocPlugins>
                    </protocPlugins>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
    <plugins>
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

2、在工程目录src/main/proto目录中创建 person.proto

插件固定编译src/main/proto.proto文件,所以文件必须放在这里

java 复制代码
syntax = "proto3";

option java_package = "com.demo.proto";
option java_multiple_files = true;
option java_outer_classname = "UserProto";

message User {
  string name = 1;
  string email = 2;
  int32 age = 3;
}

参数说明

  • java_package:生成的Java文件对应的包名
  • java_multiple_files:将实体、proto、builder类放在不同文件中
  • java_outer_classname:指定生成的proto文件名称,如果没指定就是OuterClass结尾(如UserOuterClass)

3、执行 Maven compile 编译工程生成代码

插件会根据proto文件自动在target/classes目录中生成Java文件

  • com.demo.proto.User
  • com.demo.proto.UserOrBuilder
  • com.demo.proto.UserProto

4、编写测试类

java 复制代码
public class UserProtoTest {
    @Test
    public void test() throws InvalidProtocolBufferException {
        User.Builder builder = User.newBuilder();
        builder.setName("jacky");
        builder.setAge(30);
        builder.setEmail("[email protected]");
        User user = builder.build();

        // 序列化
        byte[] serializedUser = user.toByteArray();

        // 反序列化
        Person deserializedUser = Person.parseFrom(serializedUser);
        System.out.println("--------deserialized--------");
        System.out.println(deserializedUser);
    }
}

执行结果如下:

复制代码
--------deserialized--------
name: "jacky"
email: "[email protected]"
age: 30

三、proto文件,字段类型和java类的对照

在Protocol Buffers(简称Proto)中,消息字段类型与Java类型之间存在一定的对应关系。以下是一个表格,列出了Proto中的常见消息字段类型及其在Java中的对应类型:

Proto字段类型 Java类型 备注
double double 双精度浮点数
float float 单精度浮点数
int32 int 使用可变长度编码,对于有符号整数,如果你的字段可能包含负数,则建议使用sint32
sint32 int 使用可变长度编码,有符号整数
uint32 int 使用可变长度编码,无符号整数
int64 long 使用可变长度编码,对于有符号整数,如果你的字段可能包含负数,则建议使用sint64
sint64 long 使用可变长度编码,有符号整数
uint64 long 使用可变长度编码,无符号整数
fixed32 int 总是4个字节,如果数值总是比2^28大的话,这个类型会比uint32更高效
fixed64 long 总是8个字节,如果数值总是比2^56大的话,这个类型会比uint64更高效
sfixed32 int 总是4个字节
sfixed64 long 总是8个字节
bool boolean 布尔值
string String 字符串,UTF-8编码
bytes ByteString 字节序列

注意:

  1. 对于整数类型,Proto中的int32int64类型在Java中分别对应intlong类型,但由于Java没有无符号整数类型,因此在使用时需要注意。对于有符号整数且可能包含负值的情况,建议使用sint32sint64类型,它们在编码时会处理符号位。

  2. fixed32fixed64类型分别总是占用4个字节和8个字节的空间,无论其实际值的大小如何。这在某些情况下可以提高编码效率,特别是当你知道数值范围时。

  3. sfixed32sfixed64类型也分别占用固定的4个字节和8个字节空间,但它们用于有符号整数。

  4. string类型在Java中对应String类,而bytes类型则对应ByteString类,后者是Protobuf提供的一个专门用于处理字节序列的类。

四、proto文件语法

.proto文件,用于定义数据结构和服务接口。以下是.proto文件的语法详解:

1. 文件结构

.proto文件由一系列定义组成,包括语法声明、包声明、导入声明、消息类型定义、枚举类型定义、服务接口定义以及选项声明。

2. 语法声明

语法声明用于指定.proto文件使用的Proto版本。例如:

proto 复制代码
syntax = "proto3";

目前常用的版本有proto2proto3,其中proto3是更现代、更简洁的语法。

3. 包声明

包声明用于指定.proto文件中定义的类型所属的命名空间。例如:

proto 复制代码
package example;

在Proto中,包名用于避免不同.proto文件中定义的类型之间的名称冲突。

4. 导入声明

导入声明用于导入其他.proto文件中定义的类型。例如:

proto 复制代码
import "other_proto_file.proto";

导入声明使得当前.proto文件可以使用其他文件中定义的消息类型、枚举类型和服务接口。

5. 消息类型定义

消息类型定义用于定义数据结构。例如:

proto 复制代码
message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
}

在消息类型定义中,每个字段都有一个类型、一个名称和一个唯一的编号。字段编号用于在序列化过程中识别字段。

6. 枚举类型定义

枚举类型定义用于定义一组命名的整数常量。例如:

proto 复制代码
enum PhoneType {
  MOBILE = 0;
  HOME = 1;
  WORK = 2;
}

枚举类型中的每个常量都有一个名称和一个整数值。第一个枚举值的整数值必须为0。

7. 服务接口定义

服务接口定义用于定义RPC(远程过程调用)方法。例如:

proto 复制代码
service AddressBook {
  rpc GetPerson(Person) returns (Person);
}

在服务接口定义中,每个RPC方法都有一个请求类型、一个响应类型和一个方法名。请求类型和响应类型都是之前定义的消息类型。

8. 选项声明

选项声明用于设置文件级别的选项,如生成代码的特定语言设置。例如:

proto 复制代码
option java_package = "com.example.proto";
option java_outer_classname = "ProtoExample";
option java_multiple_files = true;

这些选项影响生成的代码,如Java代码的包名和外部类名。

总结

.proto文件的语法相对简单,主要由一系列定义组成。通过定义消息类型、枚举类型和服务接口,.proto文件描述了数据的结构和服务的接口。然后,可以使用Proto编译器将这些定义编译成各种语言的代码,以便在应用程序中使用。

参考

相关推荐
腥臭腐朽的日子熠熠生辉39 分钟前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
ejinxian41 分钟前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之1 小时前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码1 小时前
Spring Task 定时任务
java·前端·spring
俏布斯1 小时前
算法日常记录
java·算法·leetcode
27669582921 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿
爱的叹息1 小时前
Java 连接 Redis 的驱动(Jedis、Lettuce、Redisson、Spring Data Redis)分类及对比
java·redis·spring
程序猿chen2 小时前
《JVM考古现场(十五):熵火燎原——从量子递归到热寂晶壁的代码涅槃》
java·jvm·git·后端·java-ee·区块链·量子计算
松韬2 小时前
Spring + Redisson:从 0 到 1 搭建高可用分布式缓存系统
java·redis·分布式·spring·缓存
绝顶少年2 小时前
Spring Boot 注解:深度解析与应用场景
java·spring boot·后端