java集成GRPC

Java集成GRPC

什么是grpc?

GRPC是一个高性能、开源的RPC(远程过程调用)框架,由Google开发并于2015年发布。支持多种语言,如Java、Python、Go等,并且能够自动生成客户端和服务器端的代码。它使用HTTP/2协议进行通信,并默认使用Google的Protocol Buffer进行数据的序列化和反序列化。
GRPC的主要特点包括:

  • 基于HTTP/2协议,支持双向流、头部压缩和多路复用等特性
  • 支持多种序列化协议,如Protobuf、JSON等
  • 自动生成客户端和服务器端的代码,简化开发流程
  • 良好的跨语言支持

本文将教你如何在java项目中整合GRPC。我们将分步骤逐步引导你完成整个过程,确保你能够理解每一步和相关的代码。

完整的代码在我的 github 上,可以查看下载。

引入依赖

在pom文件中添加如下依赖项:
由于没有官方支持的 gRPC 和 Spring Boot 集成的 starter,我们将选择较流行的第三方项目 - grpc-spring-boot-starter。该项目在 GitHub 上有大约 3.1k 个星星。

<dependency>
    <groupId>net.devh</groupId>
    <artifactId>grpc-spring-boot-starter</artifactId>
    <version>2.15.0.RELEASE</version>
</dependency>

定义.proto文件

创建.proto文件,这些文件描述了服务接口和消息格式

syntax = "proto3";

option java_multiple_files = true;
package helloworld;
option java_package = "com.example.grpc";
option java_outer_classname = "HelloWorldProto";

// 问好类
service Greeter {
  // 问候方法
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// 请求类
message HelloRequest {
  string name = 1;
}

// 回应类
message HelloReply {
  string message = 1;
}

引入maven插件

为了从*.proto schema 生成 Java 类,我们将使用 Maven 插件。

引入插件protoc-jar-maven-plugin

你可以选择一些可用的插件。我选择的是 protoc-jar-maven-plugin 插件。在配置中,我们需要将 *.proto schema 的默认位置覆写为 src/main/proto(指定./proto文件路径)。如果我们还引入了其他的.proto文件,就还需要使用 includeDirectories 标签,指定路径,加入其他 Protobuf schema。默认情况下:输出的 target 目录是 src/main/generated。默认情况下,插件不会生成 gRPC service。为了启用它,我们需要在 outputTarget 中包含 grpc-java 类型。为了生成类,我们将使用 protoc-gen-grpc-java 库。

<plugin>
    <groupId>com.github.os72</groupId>
    <artifactId>protoc-jar-maven-plugin</artifactId>
    <version>3.11.4</version>
    <executions>
        <execution>
            <phase>generate-sources</phase>
            <goals>
                <goal>run</goal>
            </goals>
            <configuration>
                <addProtoSources>all</addProtoSources>
                <includeMavenTypes>direct</includeMavenTypes>
                <outputDirectory>src/main/generated</outputDirectory>
                <inputDirectories>
                    <!-- 指定./proto 文件路径 -->
                    <include>src/main/proto</include>
                </inputDirectories>
                <!-- import的其他./proto 文件路径 -->
                <!-- <includeDirectories>
                    <include>src/main/proto-imports</include>
                </includeDirectories> -->
                <!-- 指定生成的文件路径。默认在target下 -->
                <outputTargets>
                    <outputTarget>
                        <type>java</type>
                        <outputDirectory>src/main/generated</outputDirectory>
                    </outputTarget>
                    <outputTarget>
                        <type>grpc-java</type>
                        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.57.2</pluginArtifact>
                        <outputDirectory>src/main/generated</outputDirectory>
                    </outputTarget>
                </outputTargets>
            </configuration>
        </execution>
    </executions>
</plugin>

引入插件build-helper-maven-plugin

我们还将使用 build-helper-maven-plugin Maven 插件将生成的 Java 代码作为源代码附加到指定目录下。

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <executions>
        <execution>
            <id>add-source</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>add-source</goal>
            </goals>
            <configuration>
                <sources>
                    <source>src/main/generated</source>
                </sources>
            </configuration>
        </execution>
    </executions>
</plugin>

执行mvn clean package 命令(注意: 在这里需要的maven版本需要是3.6.3以上版本)后,Maven 将生成所需的 Java 类。下面是生成 Java 类后,应用程序中目录的最终结构。

$ tree
.
├── pom.xml
└── src
    ├── main
    │   ├── generated
    │   │   └── com
    │   │       └── example
    │   │           └── grpc
    │   │               ├── GreeterGrpc
    │   │               ├── HelloReply
    │   │               ├── HelloReplyOrBuilder
    │   │               ├── HelloRequest
    │   │               ├── HelloRequestOrBuilder
    │   │               └── HelloWorldProto
    │   ├── java
    │   │   └── com
    │   │       └── test
    │   │           ├── client
    │   │           ├── controller
    │   │           └── server
    │   ├── proto
    │   │   └── account.proto
    │   ├── proto-imports
    │   │   └── XXX.proto
    │   └── resources
    └── test
        └── java

编写客户端代码

在client中进行编写客户端代码

如下是一个简单的示例:

import com.example.grpc.GreeterGrpc;
import com.example.grpc.HelloReply;
import com.example.grpc.HelloRequest;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;

import java.util.concurrent.TimeUnit;

public class HelloWorldClient {

    private final ManagedChannel channel;
    private final GreeterGrpc.GreeterBlockingStub blockingStub;

    public HelloWorldClient(String host, int port) {
        channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();

        blockingStub = GreeterGrpc.newBlockingStub(channel);
    }

    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }

    public void greet(String name) {
        HelloRequest request = HelloRequest.newBuilder().setName(name).build();
        HelloReply response;
        try {
            response = blockingStub.sayHello(request);
        } catch (StatusRuntimeException e) {
            System.out.println("RPC failed: {0}");
            return;
        }
        System.out.println(("Message from gRPC-Server: " + response.getMessage()));
    }

    public static void main(String[] args) throws InterruptedException {
        HelloWorldClient client = new HelloWorldClient("127.0.0.1", 50051);
        try {
            String user = "world";
            client.greet(user);
        } finally {
            client.shutdown();
        }
    }
}

编写服务端代码

在server中进行编写服务端代码

如下是一个简单的示例:

import com.example.grpc.GreeterGrpc;
import com.example.grpc.HelloReply;
import com.example.grpc.HelloRequest;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;

public class HelloWorldServer {
    private int port = 50051;
    private Server server;

    private void start() throws IOException {
        server = ServerBuilder.forPort(port).addService(new GreeterImpl()).build().start();
        System.out.println("服务启动");
        Runtime.getRuntime().addShutdownHook(new Thread() {

            @Override
            public void run() {

                System.err.println("*** shutting down gRPC server since JVM is shutting down");
                HelloWorldServer.this.stop();
                System.err.println("*** server shut down");

            }
        });
    }

    private void stop() {
        if (server != null) {
            server.shutdown();
        }
    }

    // block 一直到退出程序
    private void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {

        final HelloWorldServer server = new HelloWorldServer();
        server.start();
        server.blockUntilShutdown();
    }

    // 实现 定义一个实现服务接口的类
    private class GreeterImpl extends GreeterGrpc.GreeterImplBase {
        @Override
        public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
            HelloReply reply = HelloReply.newBuilder().setMessage(("Hello " + req.getName())).build();
            responseObserver.onNext(reply);
            responseObserver.onCompleted();
            System.out.println("Message from gRPC-Client:" + req.getName());
        }
    }
}

完整的pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.test</groupId>
    <artifactId>grpc</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.7.3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- SpringBoot Web容器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>net.devh</groupId>
            <artifactId>grpc-spring-boot-starter</artifactId>
            <version>2.15.0.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>com.github.os72</groupId>
                <artifactId>protoc-jar-maven-plugin</artifactId>
                <version>3.11.4</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <addProtoSources>all</addProtoSources>
                            <includeMavenTypes>direct</includeMavenTypes>
                            <outputDirectory>src/main/generated</outputDirectory>
                            <inputDirectories>
                                <include>src/main/proto</include>
                            </inputDirectories>
                            <!--                        <includeDirectories>
                                                        <include>src/main/proto-imports</include>
                                                    </includeDirectories>-->
                            <outputTargets>
                                <outputTarget>
                                    <type>java</type>
                                    <outputDirectory>src/main/generated</outputDirectory>
                                </outputTarget>
                                <outputTarget>
                                    <type>grpc-java</type>
                                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.57.2</pluginArtifact>
                                    <outputDirectory>src/main/generated</outputDirectory>
                                </outputTarget>
                            </outputTargets>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>add-source</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>add-source</goal>
                        </goals>
                        <configuration>
                            <sources>
                                <source>src/main/generated</source>
                            </sources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

到这里就完成了!有问题的话,欢迎大家指导交流、共同进步!!!

扩展知识

在Protocol Buffers中定义请求类或响应类时,你可以使用多种数据类型。以下是一些常见的数据类型,它们可以被用来定义消息字段:
标量类型

  • double: 双精度浮点数
  • float: 单精度浮点数
  • int32: 使用变长编码。对于负数效率不高,如果你的变量可能是负数,请使用sint32代替。
  • int64: 使用变长编码。对于负数效率不高,如果你的变量可能是负数,请使用sint64代替。
  • uint32: 使用变长编码的无符号32位整数。
  • uint64: 使用变长编码的无符号64位整数。
  • sint32: 使用变长编码的有符号32位整数。这些比常规的int32更有效地编码负数。
  • sint64: 使用变长编码的有符号64位整数。这些比常规的int64更有效地编码负数。
  • fixed32: 总是4字节。如果值经常大于2^28,这比uint32更有效。
  • fixed64: 总是8字节。如果值经常大于2^56,这比uint64更有效。
  • sfixed32: 总是4字节。
  • sfixed64: 总是8字节。
  • bool: 布尔值。
  • string: 字符串必须始终包含UTF-8编码或7位ASCII文本。
  • bytes: 可以包含任意字节序列。

复合类型

  • enum: 枚举类型,允许你通过一组命名的常量来定义一个类型。
  • message: 可以嵌套在其他消息定义中,用来定义复杂的数据结构。
    特殊字段
  • map: 映射类型,允许你定义键值对,键可以是任何整数或字符串类型(除了浮点类型和bytes),值可以是任何类型除了map。

以下是一个示例,展示了如何在消息定义中使用这些类型:

message ExampleMessage {
  // 标量类型
  double double_field = 1;
  float float_field = 2;
  int32 int32_field = 3;
  int64 int64_field = 4;
  uint32 uint32_field = 5;
  uint64 uint64_field = 6;
  sint32 sint32_field = 7;
  sint64 sint64_field = 8;
  fixed32 fixed32_field = 9;
  fixed64 fixed64_field = 10;
  sfixed32 sfixed32_field = 11;
  sfixed64 sfixed64_field = 12;
  bool bool_field = 13;
  string string_field = 14;
  bytes bytes_field = 15;

  // 枚举类型
  enum EnumType {
    DEFAULT = 0;
    TYPE_A = 1;
    TYPE_B = 2;
  }
  EnumType enum_field = 16;

  // 嵌套消息类型
  message NestedMessage {
    string nested_field = 1;
  }
  NestedMessage nested_message_field = 17;

  // 映射类型
  map<string, int32> map_field = 18;
}

在Protocol Buffers中,还定义一个字段来存储重复的值,这可以看作是列表(list)的概念。使用 repeated 关键字可以定义一个可以包含多个相同类型元素的集合。以下是如何定义一个列表的示例:

syntax = "proto3";  // 使用Protocol Buffers版本3

// 定义一个消息,其中包含一个字符串列表
message ExampleMessage {
  repeated string items = 1;  // "items" 是一个字符串列表
}

在这个例子中,items 字段是一个 repeated 字段,这意味着它可以包含任意数量的字符串值。当你序列化和反序列化这个消息时,items 字段将作为一个列表来处理。

在生成的代码中,items 将通常映射到编程语言中的数组或列表类型。例如,在Python中,items 将是一个列表,在Java中,它将是一个 List。

这些类型可以灵活地组合来定义复杂的消息结构,满足不同的业务需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值