上篇文章介绍了如何通过一个简单的maven项目实现gRPC客户端与服务端通信协议测试。但是这个只是一个普通的Java工程,我们在Java web开发的今天大多数Java程序员都是通过SpringCloud及SpringBoot开启微服务或者单个应用的开发,我们最好通过springboot继承gRPC,这样一个工程既可以支持web开发,也可以支持gRPC协议通信。庆幸的是有大佬yidongnan早为我们准备好了grpc相关starter,对应的开源代码地址如下:
https://github.com/yidongnan/grpc-spring-boot-starter
grpc-client-spring-boot-starter:client端start,实现自动注入stub的实现
grpc-client-spring-boot-autoconfigure:client端grpc Springboot配置文件自动加载注入
grpc-server-spring-boot-starter:server端启动starter,实现自动注入XXXServiceGrpc
grpc-server-spring-boot-autoconfigure:server端grpc springboot配置文件自动加载注入
这里是大佬yidongnan维护的项目:gRPC-Spring-Boot-Starter 文档及源码
具体server端实现和client端实现完全可以参考大佬的examples,完全足够让你理解其实现的便捷性,你也可以按照其参考实现,提供maven和gradle两种构建方式。如果你在看的过程中还有些许不懂,下面是我按照自己的理解把之前gRPC逻辑重新实现了一下,看这篇文章之前,建议先看一下我之前的一篇文章:
【JAVA】protobuf在Java中代码测试用例-CSDN博客
我们主要做以下工作
创建一个父项目(springboot-rpc),包括以下几个module
一、创建父项目
1.父项目pom.xml,在springboot-rpc工程配置子module需要用到的maven依赖
org.example
springboot-rpc
1.0-SNAPSHOT
pom
rpc-interface
rpc-server
rpc-client
UTF-8
1.59.1
3.24.0
3.24.0
1.8
1.8
io.grpc
grpc-netty-shaded
${grpc.version}
runtime
io.grpc
grpc-protobuf
${grpc.version}
io.grpc
grpc-services
${grpc.version}
io.grpc
grpc-stub
${grpc.version}
org.apache.tomcat
annotations-api
6.0.53
provided
com.google.protobuf
protobuf-java
${protobuf.version}
com.google.protobuf
protobuf-java-util
${protobuf.version}
org.apache.maven.plugins
maven-compiler-plugin
3.8.1
1.8
UTF-8
二、定义接口程序
1.定义接口定义程序rpc-interface maven依赖配置pom.xml
org.example
springboot-rpc
1.0-SNAPSHOT
rpc-interface
UTF-8
1.8
1.8
io.grpc
grpc-protobuf
io.grpc
grpc-stub
com.google.protobuf
protobuf-java
com.google.protobuf
protobuf-java-util
jakarta.annotation
jakarta.annotation-api
1.3.5
true
kr.motd.maven
os-maven-plugin
1.7.1
org.codehaus.mojo
build-helper-maven-plugin
3.3.0
test
generate-sources
add-source
com.github.os72
protoc-jar-maven-plugin
3.11.4
generate-sources
run
protoc
true
${protobuf.version}
true
src/main/protobuf
java
grpc-java
io.grpc:protoc-gen-grpc-java:1.48.1
src/main/java
2.定义proto文件
生成源码需要依赖proto文件,文件内容如下:
syntax="proto3";
option go_package="./;student"; //关于最后生成的go文件是处在哪个目录哪个包中,.代表在当前目录生成,student代表了生成的go文件的包名是student
option java_multiple_files = true; //表示下面的message需要编译成多个java文件
option java_package = "grpc.student"; //指定该proto文件编译成的java源文件的包名
option java_outer_classname = "StudentProto"; // 表示下面的message编译成的java类文件的名字
package student; // 定义作用域
service DemoService {
rpc Sender(StudentRequest) returns (StudentResponse){}
}
message StudentRequest {
string Id = 1;
}
message StudentResponse {
bytes result =1;
}
message Student {
int64 Id = 1;
string Name =2;
string No =3;
}
3.生成源码程序
依赖student.proto文件,执行编译生成源码文件,执行如下命令
mvn clean compile #当前rpc-interface工程执行
将生成的源码拷贝到src/main/grpc/student包下,工程结构如下:
rpc-interface
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── grpc
│ │ └── student
│ │ ├── DemoServiceGrpc.java
│ │ ├── Student.java
│ │ ├── StudentOrBuilder.java
│ │ ├── StudentProto.java
│ │ ├── StudentRequest.java
│ │ ├── StudentRequestOrBuilder.java
│ │ ├── StudentResponse.java
│ │ └── StudentResponseOrBuilder.java
│ ├── protobuf
│ │ └── student.proto
│ └── resources
三、创建gRPC Server服务
server端为了能依赖spring自动注入我们需要的Service实现,需要加入依赖grpc-server-spring-boot-starter,此starter自动根据springboot配置文件自动注入我们需要的service。
1.rpc-server pom.xml配置如下
org.example
springboot-rpc
1.0-SNAPSHOT
rpc-server
net.devh
grpc-server-spring-boot-starter
2.15.0.RELEASE
org.springframework.boot
spring-boot-starter-web
2.7.16
org.example
rpc-interface
1.0-SNAPSHOT
2.rpc-server配置文件application.yml
spring:
application:
name: student-rpc-server #rpc client根据此名称配置
server:
servlet:
context-path: /
port: 9999
#grpc server端暴露的端口
grpc:
server:
port: 10005
3.编写server端的接口实现
在rpc- interface中定义的接口sender()方法需要在这里实现,并标注@GrpcService注解,代码如下:
@GrpcService
public class RpcServerImpl extends DemoServiceGrpc.DemoServiceImplBase {
private Logger log = LoggerFactory.getLogger(RpcServerImpl.class.getName());
@Override
public void sender(StudentRequest request, StreamObserver responseObserver) {
if (Strings.isNullOrEmpty(request.getId())){
log.warn("request get param id is null");
return;
}
int id = Integer.parseInt(request.getId());
//build a student object then serialize it
// Student student = Student.newBuilder().build().;
Student.Builder builder = Student.newBuilder();
builder.setId(id);
builder.setName("easton");
builder.setNo("10001");
Student student = builder.build();
//try catch 属于测试序列化与反序列化代码块
ByteString jsonBs = null;
try {
//protobuf 序列化
String jsonStr = JsonFormat.printer().print(student);
log.info("json format:"+jsonStr);
jsonBs = ByteString.copyFromUtf8(jsonStr);
//反序列化
byte[] bytes = student.toByteArray();
Student student1 = Student.parseFrom(bytes);
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
// ByteString bs = student.toByteString();
StudentResponse response = StudentResponse.newBuilder().setResult(jsonBs).build();
responseObserver.onNext(response);
//需要告诉客户端数据写完,否则客户端会一直等待数据回传结束
responseObserver.onCompleted();
log.info("server method run finish");
}
}
4.编写rpc-server启动类
@SpringBootApplication
public class BootStrarpApplication {
public static void main(String[] args) {
SpringApplication.run(BootStrarpApplication.class,args);
}
}
四、创建gRPC client服务
创建完server端后接着编写客户端springboot服务
1.rpc-client pom.xml配置
跟服务端相对应需要引入依赖:grpc-client-spring-boot-starter,此starter会自动注入客户端stub实现,依赖配置为:
org.example
springboot-rpc
1.0-SNAPSHOT
rpc-client
net.devh
grpc-client-spring-boot-starter
2.15.0.RELEASE
org.springframework.boot
spring-boot-starter-web
2.7.16
org.example
rpc-interface
1.0-SNAPSHOT
2.rpc-client stub注入的代码调用实现
定义方法,此方法用来gRPC调用服务端sender()方法
@Service
public class StudentClientService {
//初始化student-rpc-server对应的stub,如果需要多个可以在这里注入
@GrpcClient("student-rpc-server")
private DemoServiceGrpc.DemoServiceBlockingStub blockingStub;
Logger logger = LoggerFactory.getLogger(StudentClientService.class);
public String sendToServer(int id){
logger.info("Will try to send " + id + " ...");
StudentRequest request = StudentRequest.newBuilder().setId(String.valueOf(id)).build();
StudentResponse response;
try{
response = blockingStub.sender(request);
}catch (StatusRuntimeException e){
e.printStackTrace();
logger.warn("RPC failed: {0}", e.getStatus());
return "";
}
ByteString byteString = response.getResult();
String result = byteString.toStringUtf8();
logger.info("Result: " +result);
return result;
}
}
3.rpc-client启动类
@SpringBootApplication
public class BootStrapApplication {
public static void main(String[] args) {
SpringApplication.run(BootStrapApplication.class,args);
}
}
4.rpc-client 配置文件application.yml配置
server:
port: 8080
servlet:
context-path: /
spring:
application:
name: student-rpc-client
grpc:
client:
student-rpc-server: #服务名称不能写错,这个需要和server端spring.application.name的定义的名称一致,否默认的negotiationType:tls
address: 'static://localhost:10005'
# 是否开启保持连接(长连接)
enableKeepAlive: true
# 保持连接时长(默认20s)
keepAliveTimeout: 20s
# 没有RPC调用时是否保持连接(默认false,可禁用避免额外消耗CPU)
keepAliveWithoutCalls: false
# 客户端负载均衡策略(round_robin(默认), pick_first)
defaultLoadBalancingPolicy: round_robin
# 通信类型
# plaintext | plaintext_upgrade | tls
# 明文通信且http/2 | 明文通信且升级http/1.1为http/2 | 使用TLS(ALPN/NPN)通信
negotiationType: plaintext
# GLOBAL: 可以指定所有grpc通用配置
5.定义一个测试接口
由于rpc- client开启了web服务,为了模拟接口调用,以Restful协议请求rpc-client http接口,http接口调用grpc server服务,创建接口如下:
@RestController
public class DemoClientController {
@Autowired
private StudentClientService clientService;
@RequestMapping(value = "/getResult",method = RequestMethod.GET)
public String getResult(@RequestParam("id") String id){
return clientService.sendToServer(Integer.valueOf(id));
}
}
五、调用测试
1.启动服务端rpc-server
2.启动客户端rpc-client
3.通过浏览器URL请求测试:http://localhost:8080/getResult?id=111
{ "Id": "111", "Name": "easton", "No": "10001" }