欢迎访问我的 GitHub
https://github.com/zq2599/blog_demos
内容: 所有原创文章分类汇总及配套源码, 涉及 Java,Docker,Kubernetes,DevOPS 等;
本篇概览
本文是《java 版 gRPC 实战》系列的第二篇, 前文《用 proto 生成代码》将父工程, 依赖库版本, helloworld.proto 对应的 java 代码都准备好了, 今天的任务是实战 gRPC 服务的开发和调用, 实现的效果如下图:
本篇的具体操作如下:
开发名为 < font color="blue">local-server</font > 的 springboot 应用, 提供 helloworld.proto 中定义的 gRPC 服务;
开发名为 < font color="blue">local-client</font > 的 springboot 应用, 调用 < font color="blue">local-server</font > 提供的 gRPP 服务;
验证 gRPC 服务能不能正常调用;
源码下载
本篇实战中的完整源码可在 GitHub 下载到, 地址和链接信息如下表所示 (https://github.com/zq2599/blo...:
名称 | 链接 | 备注 |
---|---|---|
项目主页 | https://github.com/zq2599/blo... | 该项目在 GitHub 上的主页 |
git 仓库地址(https) | https://github.com/zq2599/blo... | 该项目源码的仓库地址,https 协议 |
git 仓库地址(ssh) | mailto:git@github.com :zq2599/blog_demos.git | 该项目源码的仓库地址,ssh 协议 |
这个 Git 项目中有多个文件夹,《java 版 gRPC 实战》系列的源码在 < font color="blue">grpc-tutorials</font > 文件夹下, 如下图红框所示:
<font color="blue">grpc-tutorials</font > 文件夹下有多个目录, 本篇文章对应的代码在 < font color="blue">local-server</font > 和 < font color="blue">local-client</font > 中, 如下图红框:
开发 gRPC 服务端
首先要开发的是 gRPC 服务端, 回顾前文中 helloworld.proto 中定义的服务和接口, 如下所示, 名为 Simple 的服务对外提供名为 SayHello 接口, 这就是咱们接下来的任务, 创建一个 springboot 应用, 该应用以 gRPC 的方式提供 SayHello 接口给其他应用远程调用:
- service Simple {
- // 接口定义
- rpc SayHello (HelloRequest) returns (HelloReply) {
- }
- }
基于 springboot 框架开发一个普通的 gRPC 服务端应用, 一共需要五个步骤, 如下图所示, 接下来我们按照下图序号的顺序来开发:
首先是在父工程 < font color="blue">grpc-turtorials</font > 下面新建名为 < font color="">local-server</font > 的模块, 其 build.gradle 内容如下:
- // 使用 springboot 插件
- plugins {
- id 'org.springframework.boot'
- }
- dependencies {
- implementation 'org.projectlombok:lombok'
- implementation 'org.springframework.boot:spring-boot-starter'
- // 作为 gRPC 服务提供方, 需要用到此库
- implementation 'net.devh:grpc-server-spring-boot-starter'
- // 依赖自动生成源码的工程
- implementation project(':grpc-lib')
- }
这是个 springboot 应用, 配置文件内容如下:
- spring:
- application:
- name: local-server
- # gRPC 有关的配置, 这里只需要配置服务端口号
- grpc:
- server:
- port: 9898
新建拦截类 < font color="blue">LogGrpcInterceptor.java</font>, 每当 gRPC 请求到来后该类会先执行, 这里是将方法名字在日志中打印出来, 您可以对请求响应做更详细的处理:
- package com.bolingcavalry.grpctutorials;
- import io.grpc.Metadata;
- import io.grpc.ServerCall;
- import io.grpc.ServerCallHandler;
- import io.grpc.ServerInterceptor;
- import lombok.extern.slf4j.Slf4j;
- @Slf4j
- public class LogGrpcInterceptor implements ServerInterceptor {
- @Override
- public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata,
- ServerCallHandler<ReqT, RespT> serverCallHandler) {
- log.info(serverCall.getMethodDescriptor().getFullMethodName());
- return serverCallHandler.startCall(serverCall, metadata);
- }
- }
为了让 LogGrpcInterceptor 可以在 gRPC 请求到来时被执行, 需要做相应的配置, 如下所示, 在普通的 bean 的配置中添加注解即可:
- package com.bolingcavalry.grpctutorials;
- import io.grpc.ServerInterceptor;
- import.NET.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor;
- import org.springframework.context.annotation.Configuration;
- @Configuration(proxyBeanMethods = false)
- public class GlobalInterceptorConfiguration {
- @GrpcGlobalServerInterceptor
- ServerInterceptor logServerInterceptor() {
- return new LogGrpcInterceptor();
- }
- }
应用启动类很简单:
- package com.bolingcavalry.grpctutorials;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- @SpringBootApplication
- public class LocalServerApplication {
- public static void main(String[] args) {
- SpringApplication.run(LocalServerApplication.class, args);
- }
- }
接下来是最重要的 service 类, gRPC 服务在此处对外暴露出去, 完整代码如下, 有几处要注意的地方稍后提到:
- package com.bolingcavalry.grpctutorials;
- import com.bolingcavalry.grpctutorials.lib.HelloReply;
- import com.bolingcavalry.grpctutorials.lib.SimpleGrpc;
- import.NET.devh.boot.grpc.server.service.GrpcService;
- import java.util.Date;
- @GrpcService
- public class GrpcServerService extends SimpleGrpc.SimpleImplBase {
- @Override
- public void sayHello(com.bolingcavalry.grpctutorials.lib.HelloRequest request,
- io.grpc.stub.StreamObserver<com.bolingcavalry.grpctutorials.lib.HelloReply> responseObserver) {
- HelloReply reply = HelloReply.newBuilder().setMessage("Hello" + request.getName() + "," + new Date()).build();
- responseObserver.onNext(reply);
- responseObserver.onCompleted();
- }
- }
上述 GrpcServerService.java 中有几处需要注意:
是使用 < font color="blue">@GrpcService</font > 注解, 再继承 SimpleImplBase, 这样就可以借助 grpc-server-spring-boot-starter 库将 sayHello 暴露为 gRPC 服务;
SimpleImplBase 是前文中根据 proto 自动生成的 java 代码, 在 grpc-lib 模块中;
sayHello 方法中处理完毕业务逻辑后, 调用 HelloReply.onNext 方法填入返回内容;
调用 HelloReply.onCompleted 方法表示本次 gRPC 服务完成;
至此, gRPC 服务端编码就完成了, 咱们接着开始客户端开发;
调用 gRPC
在父工程 < font color="blue">grpc-turtorials</font > 下面新建名为 < font color="">local-client</font > 的模块, 其 build.gradle 内容如下, 注意要使用 spingboot 插件, 依赖 grpc-client-spring-boot-starter 库:
- plugins {
- id 'org.springframework.boot'
- }
- dependencies {
- implementation 'org.projectlombok:lombok'
- implementation 'org.springframework.boot:spring-boot-starter'
- implementation 'org.springframework.boot:spring-boot-starter-web'
- implementation 'net.devh:grpc-client-spring-boot-starter'
- implementation project(':grpc-lib')
- }
应用配置文件 grpc-tutorials/local-client/src/main/resources/application.YAML, 注意 address 的值就是 gRPC 服务端的信息, 我这里 local-server 和 local-client 在同一台电脑上运行, 请您根据自己情况来设置:
- server:
- port: 8080
- spring:
- application:
- name: local-grpc-client
- grpc:
- client:
- # gRPC 配置的名字, GrpcClient 注解会用到
- local-grpc-server:
- # gRPC 服务端地址
- address: 'static://127.0.0.1:9898'
- enableKeepAlive: true
- keepAliveWithoutCalls: true
- negotiationType: plaintext
- 接下来要创建下图展示的类, 按序号顺序创建:
首先是拦截类 LogGrpcInterceptor, 与服务端的拦截类差不多, 不过实现的接口不同:
- package com.bolingcavalry.grpctutorials;
- import io.grpc.*;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- public class LogGrpcInterceptor implements ClientInterceptor {
- private static final Logger log = LoggerFactory.getLogger(LogGrpcInterceptor.class);
- @Override
- public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
- CallOptions callOptions, Channel next) {
- log.info(method.getFullMethodName());
- return next.newCall(method, callOptions);
- }
- }
为了让拦截类能够正常工作, 即发起 gRPC 请求的时候被执行, 需要新增一个配置类:
- package com.bolingcavalry.grpctutorials;
- import io.grpc.ClientInterceptor;
- import.NET.devh.boot.grpc.client.interceptor.GrpcGlobalClientInterceptor;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.core.Ordered;
- import org.springframework.core.annotation.Order;
- @Order(Ordered.LOWEST_PRECEDENCE)
- @Configuration(proxyBeanMethods = false)
- public class GlobalClientInterceptorConfiguration {
- @GrpcGlobalClientInterceptor
- ClientInterceptor logClientInterceptor() {
- return new LogGrpcInterceptor();
- }
- }
启动类:
- package com.bolingcavalry.grpctutorials;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- @SpringBootApplication
- public class LocalGrpcClientApplication {
- public static void main(String[] args) {
- SpringApplication.run(LocalGrpcClientApplication.class, args);
- }
- }
接下来是最重要的服务类 GrpcClientService, 有几处要注意的地方稍后会提到:
- package com.bolingcavalry.grpctutorials;
- import com.bolingcavalry.grpctutorials.lib.HelloReply;
- import com.bolingcavalry.grpctutorials.lib.HelloRequest;
- import com.bolingcavalry.grpctutorials.lib.SimpleGrpc;
- import io.grpc.StatusRuntimeException;
- import.NET.devh.boot.grpc.client.inject.GrpcClient;
- import org.springframework.stereotype.Service;
- @Service
- public class GrpcClientService {
- @GrpcClient("local-grpc-server")
- private SimpleGrpc.SimpleBlockingStub simpleStub;
- public String sendMessage(final String name) {
- try {
- final HelloReply response = this.simpleStub.sayHello(HelloRequest.newBuilder().setName(name).build());
- return response.getMessage();
- } catch (final StatusRuntimeException e) {
- return "FAILED with" + e.getStatus().getCode().name();
- }
- }
- }
上述 GrpcClientService 类有几处要注意的地方:
用 < font color="blue">@Service</font > 将 GrpcClientService 注册为 spring 的普通 bean 实例;
用 < font color="blue">@GrpcClient</font > 修饰 SimpleBlockingStub, 这样就可以通过 grpc-client-spring-boot-starter 库发起 gRPC 调用, 被调用的服务端信息来自名为 < font color="red">local-grpc-server</font > 的配置;
SimpleBlockingStub 来自前文中根据 helloworld.proto 生成的 java 代码;
SimpleBlockingStub.sayHello 方法会远程调用 local-server 应用的 gRPC 服务;
为了验证 gRPC 服务调用能否成功, 再新增个 Web 接口, 接口内部会调用 GrpcClientService.sendMessage, 这样咱们通过浏览器就能验证 gRPC 服务是否调用成功了:
- package com.bolingcavalry.grpctutorials;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.Web.bind.annotation.RequestMapping;
- import org.springframework.Web.bind.annotation.RequestParam;
- import org.springframework.Web.bind.annotation.RestController;
- @RestController
- public class GrpcClientController {
- @Autowired
- private GrpcClientService grpcClientService;
- @RequestMapping("/")
- public String printMessage(@RequestParam(defaultValue = "will") String name) {
- return grpcClientService.sendMessage(name);
- }
- }
编码完成, 接下来将两个服务都启动, 验证 gRPC 服务是否正常;
验证 gRPC 服务
<font color="blue">local-server</font > 和 < font color="blue">local-client</font > 都是普通的 springboot 应用, 可以在 IDEA 中启动, 点击下图红框位置, 在弹出菜单中选择 < font color="red">Run 'LocalServerApplication'</font > 即可启动 local-server:
local-server 启动后, 控制台会提示 gRPC server 已启动, 正在监听 9898 端口, 如下图:
local-client 后, 在浏览器输入 < font color="blue">http://localhost:8080/?name=Tom</font>, 可以看到响应的内容正是来自 local-server 的 GrpcServerService.java:
从 Web 端到 gRPC 服务端的关键节点信息如下图:
可以看到 local-server 的拦截日志:
还有 local-client 的拦截日志:
至此, 最简单的 java 版 gRPC 服务发布和调用验证通过, 本篇的任务也就完成了, 接下来的文章, 咱们会继续深入学习 java 版 gRPC 的相关技术;
你不孤单, 欣宸原创一路相伴
Java 系列
Spring 系列
Docker 系列
kubernetes 系列
数据库 + 中间件系列
DevOps 系列
来源: https://segmentfault.com/a/1190000040384524