【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("jacky@example.com");
        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: "jacky@example.com"
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编译器将这些定义编译成各种语言的代码,以便在应用程序中使用。

参考

相关推荐
WaaTong8 分钟前
《重学Java设计模式》之 原型模式
java·设计模式·原型模式
m0_743048448 分钟前
初识Java EE和Spring Boot
java·java-ee
AskHarries10 分钟前
Java字节码增强库ByteBuddy
java·后端
佳佳_24 分钟前
Spring Boot 应用启动时打印配置类信息
spring boot·后端
小灰灰__30 分钟前
IDEA加载通义灵码插件及使用指南
java·ide·intellij-idea
夜雨翦春韭33 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
程序媛小果1 小时前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
追风林1 小时前
mac m1 docker本地部署canal 监听mysql的binglog日志
java·docker·mac
芒果披萨1 小时前
El表达式和JSTL
java·el
duration~2 小时前
Maven随笔
java·maven