初次接触gRPC 集成SpringBoot 在学习的过程中发现网上资料 零散 繁杂 片面
故总结此文 当做索引给后人节省时间
(文中的超链都是给大家点击查看的)
入篇之前需要了解以下名词
名词解释- gRPC:一个高性能、开源的通用RPC框架,它可以使用Protobuf定义服务
- Protobuf:协议缓冲区是一种与语言无关、与平台无关的可扩展机制,用于序列化结构化的数据(参考JSON)
- proto3:proto是一种语言规范,Protobuf就遵循这种语言规范,目前最高版本是proto3
一句话介绍:
gRPC是一款基于HTTP/2设计,支持双向流,多路复用的接口定义语言RPC框架
- gRPC官网
- gRPC-git-java(介绍里面有java的集成方法)
- gRPC官方文档(中文版)
- GRPC简介–知乎
当前gRPC接入SpringBoot有两种方式
一种是git大神为SpringBoot封装的开源包 接入方便 配置方便 但是是个人维护更新没有保障
一种是官方推荐的java通用接入方法 配置相比上面方法复杂 但是灵活度高
这里不再赘述 有大神的详细教程 甚至还有源码分析
gRPC与SpringBoot集成
在官方git下面有介绍(仅看Download部分就好,下面会介绍Generated Code部分)
gRPC官方提供的接入方法
说明:
- 注意官方的注释org.apache.tomcat 是 necessary for Java 9+ 如果使用1.8不用引入
- 调试阶段建议增加grpc-services包 开启反射 方便调试 参考gRPC 反射服务
proto介绍io.grpc grpc-services xxxx
- gRPC使用 接口定义语言(interface definition language,IDL) protobuf
- protobuf文件的后缀为.proto
- proto有proto2.0和proto3.0两个版本 已经2022年了我们本文里介绍使用的3.0版本
protocol官方文档
protocol官方文档(中文版)(找了几个,这个文档效果最好)
定义比较好理解 主要是any和oneof使用时需要留意
服务定义服务定义分为四种传输模式 gRPC官方文档-defining-the-service(可以参考上面的 gRPC官方文档(中文版) 一样的 但是中文文档没法复制指向链接)
- 一元服务(unary)
- 客户端流(client streaming)
- 服务端流(server streaming)
- 双向流(bidirectional streaming)
官方文档中有每种定义的实现方式gRPC官方文档-implementing-routeguide
不想自己研究的话 找了一个提供demo的实现教程
gRPC-Java(二):一个Demo熟悉gRPC的四种模式(demo在文章最下面)
手写proto文件 结构简单的时候还可以 遇到复杂的结构写结构就要写一天 整理了几个生成proto的方式
- 使用java实体类在线生成
根据实体类生成proto文件 但是会给带上一些个方法 需要自己再做调整 - 使用json在线生成
生成的好像是proto2的格式 也需要根据自己需求调整 - 本地代码生成
自己写的硬编码 使用反射读取实体类下面的字段 生成proto格式
当前代码仅简单映射 对于嵌套格式类和list map等不支持 可以自己改造(欢迎改造后贴到评论区)
public class Java2Proto {
public static void main(String[] args) {
java2proto(XXX.class);
}
public static void java2proto(Class aClass){
StringBuilder b = new StringBuilder();
Field[] fields = aClass.getDeclaredFields();
b.append("message ").append("").append(aClass.getSimpleName()).append("{").append("n");
for (int i = 1; i <= fields.length; i++) {
Field field = fields[i-1];
String name = field.getName();
String type = field.getType().getName();
b.append(" ");
switch (type){
case "java.lang.String":
b.append("string");
break;
case "java.lang.Double":
case "double":
b.append("double");
break;
case "java.lang.Float":
case "float":
b.append("float");
break;
case "java.lang.Long":
case "long":
b.append("int64");
break;
case "java.lang.Integer":
case "int":
b.append("int32");
break;
case "java.lang.Boolean":
case "boolean":
b.append("bool");
break;
default:
b.append(type);
}
b.append(" ").append(name).append(" ").append("=").append(" ").append(i).append(";");
b.append("n");
}
b.append("}");
b.append("n");
System.out.println(b);
}
}
代码生成插件
现在我们有了符合自己需求场景的proto文件 接下来要根据proto文件生成java的实例类和客户端
有maven 和 gradle两个版本 使用
官方git中protobuf提供的插件
可以使用命令行
编译说明
也可以使用idea提供的界面操作
生成的代码在target文件夹下 不需要拷贝到ser
如果出现 代码找不到protobuf生成文件的情况
确认下有没有把target/generated-sources/protobuf下面的包设置成sec
接入方式的代码网上比较丰富 但是protobuf各个类型的java实现使用不是很清晰
我的代码主要为了提供一个纯净的grpc脚手架 以及各个类型的赋值和取值 就仅以一元请求为例
详细的说明我加在代码的注释里 代码不用复制粘贴 本节最后会附上git地址
2.准备proto文件4.0.0 org.example grpc-server 1.0-SNAPSHOT 8 8 io.grpc grpc-netty 1.45.1 io.grpc grpc-protobuf 1.45.1 io.grpc grpc-stub 1.45.1 kr.motd.maven os-maven-plugin 1.6.2 org.xolstice.maven.plugins protobuf-maven-plugin 0.6.1 com.google.protobuf:protoc:3.19.2:exe:${os.detected.classifier} grpc-java io.grpc:protoc-gen-grpc-java:1.45.1:exe:${os.detected.classifier} compile compile-custom
proto文件中包含了全部支持的类型
syntax = "proto3";
import "google/protobuf/any.proto";
option java_multiple_files = true;
option java_package = "com.demo.grpc.api";
option java_outer_classname = "TestDemo";
package com.demo.grpc.api;
// 定义服务类型:服务接口.定义请求参数和相应结果
service DemoService {
rpc test (DemoRequest) returns (DemoResponse);
}
// 请求体
message DemoRequest {
int32 testInt = 1;
int64 testLong = 2;
string testString = 3;
repeated string testArray = 4;Ω
map testMap = 5;
google.protobuf.Any testAny = 6;
message TestNested {
int32 a = 1;
string b = 2;
}
TestNested testNested = 7;
}
// 响应体
message DemoResponse {
oneof testOneof {
int32 code = 1;
string message = 2;
};
}
3.实现server端方法体
继承并实现生成在target/generated-sources/protobuf下面的类
package com.demo.grpc;
import com.demo.grpc.api.DemoRequest;
import com.demo.grpc.api.DemoResponse;
import com.demo.grpc.api.DemoServiceGrpc;
import io.grpc.stub.StreamObserver;
import java.util.Random;
//实现生成文件里的ImplBase 并且在类里重写proto中定义的方法
public class MyDemoServiceImpl extends DemoServiceGrpc.DemoServiceImplBase {
@Override
public void test(DemoRequest request, StreamObserver responseObserver) {
try {
//打印请求
System.out.println("收到请求:");
System.out.println(request.getTestInt());
System.out.println(request.getTestLong());
System.out.println(request.getTestString());
System.out.println("list size is :" + request.getTestArrayList().size());
System.out.println("map size is :" + request.getTestMapMap().size());
System.out.println("nested.a is : " + request.getTestNested().getA());
System.out.println("nested.b is : " + request.getTestNested().getB());
System.out.println("any is : " + request.getTestAny().unpack(DemoResponse.class).getMessage());
//构建响应
DemoResponse.Builder resBuilder = DemoResponse.newBuilder();
//随机测试oneof
if(new Random().nextBoolean()){
resBuilder.setCode(200);
}else{
resBuilder.setMessage("success");
}
//发送数据
responseObserver.onNext(resBuilder.build());
//因为是请求 结束链接
responseObserver.onCompleted();
} catch (Exception e) {
System.out.println("server 方法体出现错误" + e.getMessage());
//发送错误,onError自动断开
responseObserver.onError(e);
}
}
}
4.gRPC服务启动代码
ServerBuilder下面还有一些参数设置方法 可以详细了解下
package com.demo.grpc;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import java.io.IOException;
public class GrpcServer {
public static void main(String[] args) {
Server server=null;
try {
//这里面还可以加入拦截器,过滤器,超时等
server = ServerBuilder.forPort(6666)
.addService(new MyDemoServiceImpl())
.build().start();
//持续等待消息
server.awaitTermination();
} catch (Exception e) {
System.out.println("服务端出错"+e);
} finally {
if (server!=null) {
server.shutdown();
}
}
}
}
5.gRPC客户端请求代码
package com.demo.grpc;
import com.demo.grpc.api.DemoRequest;
import com.demo.grpc.api.DemoResponse;
import com.demo.grpc.api.DemoServiceGrpc;
import com.google.protobuf.Any;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import java.util.*;
public class GrpcClient {
public static void main(String[] args) {
//构建链接信息(并没有真正连接)
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost",6666)
.usePlaintext().build();
try {
List testArray = Arrays.asList(new String[]{"item0","item1","item2","item3",});
Map testMap = new HashMap(){
{
put("testA","A");
put("testB","B");
}
};
//为了测试 any 不是真正的返回结构体
DemoResponse testAny = DemoResponse.newBuilder().setMessage("for any").build();
//构建请求信息
DemoRequest request = DemoRequest.newBuilder()
.setTestInt(1)
.setTestLong(2L)
.setTestString("string")
//添加repeated要用addList
.addAllTestArray(testArray)
//map可以直接放入Map<>
.putAllTestMap(testMap)
//也可以放入KV对
.putTestMap("testC","C")
//any可以是proto中定义的任意结构体
.setTestAny(Any.pack(testAny))
.build();
//真正创建连接
DemoServiceGrpc.DemoServiceBlockingStub stub = DemoServiceGrpc.newBlockingStub(channel);
//传入请求 接收返回
DemoResponse demoResponse = stub.test(request);
System.out.println("res code is : " + demoResponse.getCode() + ", message is : " + demoResponse.getMessage());
}finally {
channel.shutdown();
}
}
}
测试结果
服务端结果
客户端结果测试oneof 两种结果
源码git地址
接入测试 gRPCurlgRPCurl 类似curl 但是是Go语言的
Postman大家常用的接口调试工具 Postman 也支持gRPC协议
PostmangRPC功能使用介绍
nginx 代理gRPC需要特殊配置
Nginx配置gRPC Nginx配置代理gRPC的方法
在调试/业务流程中 通常客户端需要拿到服务端提供的错误信息 gRPC也提供了对应的能力
在本人开发过程中 这一部分花费了很多资料查找的时间 特补充一下
以流式请求举例 列举三种可行方案 简单举例
1.在onError时 用Status的withDescriptionresponseObserver.onError(Status.INVALID_ARGUMENT.withDescription("错误信息").asRuntimeException());
适用于简单的文字情况
2.在onError时 在StatusRuntimeException加入MetadataMetadata metadata = new Metadata();
metadata.put(Metadata.Key.of("xxx-bin",Metadata.BINARY_BYTE_MARSHALLER),"错误信息".getBytes(StandardCharsets.UTF_8));
//传统写法 两种都可以
//responseObserver.onError(new StatusRuntimeException(Status.INVALID_ARGUMENT, metadata));
responseObserver.onError(Status.INVALID_ARGUMENT.asRuntimeException(metadata));
这种方法不仅可以放入文字 还可以放入对象等丰富结构
- 需要注意的是我在开发的过程中发现 key的后缀"-bin"是必须加上的
ps.1和2可以同时使用
responseObserver.onError(Status.INVALID_ARGUMENT.withDescription("错误信息").asRuntimeException(metadata));
参考gRPC异常处理流程设计
3.自定义在proto中自定义error结构 不适用onError而用onNext下发error结构
这样做需要同时在服务端和客户端做错误情况的处理 不推荐



