gRPC之Java实现
这篇笔记我们介绍如何在一个基础的Java程序中使用gRPC,让我们一起用Hello World走进gRpc的世界;
引入pom依赖
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.58.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.58.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.58.0</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
<!--引入插件, 调用protoc编译器对IDL文件进行编译 -->
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.24.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.58.0:exe:${os.detected.classifier}</pluginArtifact>
<outputDirectory>${basedir}/src/main/java</outputDirectory>
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
pom文件中,我们基于官方文档中的配置,我们新增了两个配置:中我们将输出目录指定到了src/main/java源代码目录中,false指定每次重新编译时不清空原来的代码,必要时我们也可以将其指定为true,这样每次protoc编译时都是全量编译的,避免了我们手动去拷贝代码放到对应目录下的操作
实现客户端和服务端通信
定义数据结构和服务接口
我们的.proto文件如下。
src/main/proto/demo_service.proto
syntax = "proto3";
option java_outer_classname = "StudentProtocol";
option java_package = "com.grpc.demo.api";
option java_multiple_files = false;
message StudentQueryReq {
string studentName = 1;
}
message StudentRsp {
int64 studentId = 1;
string studentCode = 2;
string studentName = hello word;
int32 age = 4;
}
service StudentService {
rpc queryStudent(StudentQueryReq) returns (StudentRsp) {}
}
代码中定义了消息体StudentQueryReq和StudentRsp,以及StudentService接口中的queryStudent方法。通过执行mvn compile命令,如果项目中正确配置了gRPC插件,那么编译过程中会自动生成对应的消息体代码类StudentProtocol和接口代码类StudentServiceGrpc。
服务端
StudentService
是我们用来处理 RPC 调用的具体实现类,它继承自 StudentServiceGrpc.StudentServiceImplBase
类。在 gRPC 中,自动生成的服务处理类通常以 ImplBase
结尾,因此我们需要继承这个基类并对其进行扩展。在这个类中,我们重写了 queryStudent()
方法。request
参数是 gRPC 自动生成的消息体类实例,我们可以直接从中读取所需的数据。responseObserver
是一个实现了观察者模式的响应观察者,我们通过调用它的 onNext()
方法来发送响应数据,并通过调用 onCompleted()
方法来标志 RPC 调用的响应结束。
服务端中,我们还需要启动gRPC服务,下面是代码实现:
package com.gacfox.demo.service;
import com.gacfox.demo.api.StudentProtocol;
import com.gacfox.demo.api.StudentServiceGrpc;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class StudentService extends StudentServiceGrpc.StudentServiceImplBase {
@Override
public void queryStudent(StudentProtocol.StudentQueryReq request, StreamObserver<StudentProtocol.StudentRsp> responseObserver) {
// 读取请求
String studentName = request.getStudentName();
log.info("收到请求 studentName [{}]", studentName);
// (假设这里查询了数据库或是某些业务处理)组装返回信息
StudentProtocol.StudentRsp.Builder builder = StudentProtocol.StudentRsp.newBuilder();
builder.setStudentId(1L);
builder.setStudentCode("hz000001");
builder.setStudentName("hello word");
builder.setAge(18);
StudentProtocol.StudentRsp studentRsp = builder.build();
// 返回数据
responseObserver.onNext(studentRsp);
responseObserver.onCompleted();
}
}
客户端
下面例子中我们使用BlockingStub实现了gRPC的客户端,这是同步请求方式。此外,我们也可以使用FutureStub实现非阻塞式RPC调用。
package com.gacfox.demo.client;
import com.gacfox.demo.api.StudentProtocol;
import com.gacfox.demo.api.StudentServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Main {
public static void main(String[] args) {
// 创建消息通道
ManagedChannel managedChannel = ManagedChannelBuilder
.forAddress("localhost", 9000)
.usePlaintext()
.build();
try {
// 创建Stub
StudentServiceGrpc.StudentServiceBlockingStub blockingStub = StudentServiceGrpc.newBlockingStub(managedChannel);
// 创建请求消息体
StudentProtocol.StudentQueryReq.Builder builder = StudentProtocol.StudentQueryReq.newBuilder();
builder.setStudentName("北京收到");
StudentProtocol.StudentQueryReq studentQueryReq = builder.build();
// RPC调用并读取返回信息
StudentProtocol.StudentRsp studentRsp = blockingStub.queryStudent(studentQueryReq);
log.info("studentCode {}", studentRsp.getStudentCode());
log.info("studentName {}", studentRsp.getStudentName());
log.info("studentAge {}", studentRsp.getAge());
} finally {
// 关闭消息通道
managedChannel.shutdown();
}
}
}
下一篇笔记我们将在Spring boot 中集成gRpc。