欢迎访问我的GitHub

https://github.com/zq2599/blog_demos

内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;

本篇概览

  • 本文是《java版gRPC实战》系列的第二篇,前文《用proto生成代码》将父工程、依赖库版本、helloworld.proto对应的java代码都准备好了,今天的任务是实战gRPC服务的开发和调用,实现的效果如下图:

  • 本篇的具体操作如下:
  1. 开发名为local-server的springboot应用,提供helloworld.proto中定义的gRPC服务;
  2. 开发名为local-client的springboot应用,调用local-server提供的gRPP服务;
  3. 验证gRPC服务能不能正常调用;

源码下载

名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,《java版gRPC实战》系列的源码在grpc-tutorials文件夹下,如下图红框所示:

  • grpc-tutorials文件夹下有多个目录,本篇文章对应的代码在local-server和local-client中,如下图红框:

开发gRPC服务端

  • 首先要开发的是gRPC服务端,回顾前文中helloworld.proto中定义的服务和接口,如下所示,名为Simple的服务对外提供名为SayHello接口,这就是咱们接下来的任务,创建一个springboot应用,该应用以gRPC的方式提供SayHello接口给其他应用远程调用:
service Simple {
// 接口定义
rpc SayHello (HelloRequest) returns (HelloReply) {
}
}
  • 基于springboot框架开发一个普通的gRPC服务端应用,一共需要五个步骤,如下图所示,接下来我们按照下图序号的顺序来开发:

  • 首先是在父工程grpc-turtorials下面新建名为local-server的模块,其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
  • 新建拦截类LogGrpcInterceptor.java,每当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中有几处需要注意:
  1. 是使用@GrpcService注解,再继承SimpleImplBase,这样就可以借助grpc-server-spring-boot-starter库将sayHello暴露为gRPC服务;
  2. SimpleImplBase是前文中根据proto自动生成的java代码,在grpc-lib模块中;
  3. sayHello方法中处理完毕业务逻辑后,调用HelloReply.onNext方法填入返回内容;
  4. 调用HelloReply.onCompleted方法表示本次gRPC服务完成;
  • 至此,gRPC服务端编码就完成了,咱们接着开始客户端开发;

调用gRPC

  • 在父工程grpc-turtorials下面新建名为local-client的模块,其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.yml,注意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类有几处要注意的地方:
  1. 用@Service将GrpcClientService注册为spring的普通bean实例;
  2. 用@GrpcClient修饰SimpleBlockingStub,这样就可以通过grpc-client-spring-boot-starter库发起gRPC调用,被调用的服务端信息来自名为local-grpc-server的配置;
  3. SimpleBlockingStub来自前文中根据helloworld.proto生成的java代码;
  4. 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服务

  1. local-server和local-client都是普通的springboot应用,可以在IDEA中启动,点击下图红框位置,在弹出菜单中选择Run 'LocalServerApplication'即可启动local-server:

  1. local-server启动后,控制台会提示gRPC server已启动,正在监听9898端口,如下图:

  1. local-client后,在浏览器输入http://localhost:8080/?name=Tom,可以看到响应的内容正是来自local-server的GrpcServerService.java:

  1. 从web端到gRPC服务端的关键节点信息如下图:

  • 可以看到local-server的拦截日志:

  • 还有local-client的拦截日志:

  • 至此,最简单的java版gRPC服务发布和调用验证通过,本篇的任务也就完成了,接下来的文章,咱们会继续深入学习java版gRPC的相关技术;

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

欢迎关注公众号:程序员欣宸

微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界...

https://github.com/zq2599/blog_demos

java版gRPC实战之二:服务发布和调用的更多相关文章

  1. java版gRPC实战之三:服务端流

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  2. java版gRPC实战之六:客户端动态获取服务端地址

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  3. java版gRPC实战之一:用proto生成代码

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  4. java版gRPC实战之四:客户端流

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. java版gRPC实战之七:基于eureka的注册发现

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  6. java版gRPC实战之五:双向流

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  7. SpringCloud之Eureka:服务发布与调用例子

    Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的. SpringCloud将它集成在其子 ...

  8. Java JDBC学习实战(二): 管理结果集

    在我的上一篇博客<Java JDBC学习实战(一): JDBC的基本操作>中,简要介绍了jdbc开发的基本流程,并详细介绍了Statement和PreparedStatement的使用:利 ...

  9. java并发编程实战《二》java内存模型

    Java解决可见性和有序性问题:Java内存模型 什么是 Java 内存模型? Java 内存模型是个很复杂的规范,可以从不同的视角来解读,站在我们这些程序员的视角,本质上可以理解为, Java 内存 ...

随机推荐

  1. Java进阶 | 从整体上观察面向对象

    一.面向对象 面向对象是Java编程中最核心的思想,基本特征:继承.封装.多态. 1.特征之封装 将结构.数据.操作封装在对象实体中,使用时可以不关注对象内部结构,只能访问开放权限的功能入口,从而降低 ...

  2. RSA算法之学习

    一.RSA算法 RSA是非对称加密算法中的代表,它的重要性不言而喻,为了弄清楚RSA算法,我们一起来完成一项任务: 背景:现在是疫情时代,假如小明和女朋友被迫在两个城市,小明为了表达感情,想发给对方一 ...

  3. [GXYCTF2019]Ping Ping Ping(ping命令执行绕过Waf)

    记一道ping注入的题.过滤了很多字符. 分析 简单的测了一下,很容易就拿到了flag.php和index.php. 但是存在waf无法直接查看.直接?ip=127.0.0.1|cat flag.ph ...

  4. Android达到什么水平才能顺利拿到 20k 无压力?

    程序员分很多种类和等级,如果要提高达到20k的概率,有两个条件如果满足的话,则很容易达到: 1.一线城市:北上广深杭 2.互联网行业 如果你非得抬杠,我要在三线城市,做外包要赚20k的话,很难,我自己 ...

  5. Nacos 笔记

    Nacos 笔记 目录 Nacos 笔记 1. Nacos简介 1.1 主流配置中心对比 1.2 主流注册中心对比 1.3 Nacos特性 2. 安装启动 支持外部 MySQL 3. 配置管理 3.1 ...

  6. Guava Cache 原理分析与最佳实践

    前言 目前大部分互联网架构 Cache 已经成为了必可不少的一环.常用的方案有大家熟知的 NoSQL 数据库(Redis.Memcached),也有大量的进程内缓存比如 EhCache .Guava ...

  7. FIFO 深度了解

    嘻哈第二篇,深度聊聊各种细节. 优化与跨时钟阈分析

  8. 自定义流式布局:ViewGroup的测量与布局

    目录 1.View生命周期以及View层级 1.1.View生命周期 1.2.View层级 2.View测量与MeasureSpec类 2.1.MeasureSpec类 2.2.父View的限制 :测 ...

  9. Java Lambda 表达式源码分析

    基本概念 Lambda 表达式 函数式接口 方法引用 深入实现原理 字节码 为什么不使用匿名内部类? invokedynamic 总结 参考链接 GitHub 项目 Lambda 表达式是什么?JVM ...

  10. noip38

    T1 有个朴素的暴力,枚举每一个子矩形,复杂度 \(O(n^{2}m^{2})\),观察数据范围,n很小,考虑枚举行,对于 \(m\) 用 \(two\;pointers\) 来维护. 先预处理出每一 ...