目录
一、gRPC 整合 SpringBoot 2.6.3
1.1、创建项目
- api:编写 proto 文件(message 和 service),生成 Java 代码.
- client:引入 api 模块,客户端,通过 stub 代理对 server 进行远程调用.
- server:引入 api 模块,服务端,实现 proto 文件中描述 service,为 client 提供服务.
1.2、前置配置
a)protoc 版本如下:(上章节讲过如何配置)
Ps:此处的版本一定要和后面的依赖、构建插件一一对应起来,否则生成的 Java 的文件有问题.
b)api 中的 proto 文件夹(名字必须是 proto,目录位置不能变!)
1.3、api 开发
a)
依赖如下:
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.51.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.51.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.51.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>
</dependencies>
构建脚本如下(这里一定要和 protoc 版本对应起来,否则生成的 Java 文件有问题):
<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.21.7:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.52.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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
b)proto 文件如下
syntax = "proto3";
option java_multiple_files = false;
option java_package = "com.cyk";
option java_outer_classname = "HelloProto";
service HelloService {
rpc hello(HelloRequest) returns(HelloResponse) {};
}
message HelloRequest {
string msg = 1;
}
message HelloResponse {
string result = 1;
}
1.4、server 开发
a)pom.xml 文件如下:
<dependencies>
<dependency>
<groupId>com.cyk</groupId>
<artifactId>api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
</dependencies>
b )配置文件如下:
spring:
application:
name: boot_server
# 不启动 Tomcat 容器: server 这边只需要提供gRPC远程调用服务即可
# ,Tomcat 这种 Web 服务用不上启动反而浪费额外资源和端口
main:
web-application-type: none
# gRPC 启动端口
grpc:
server:
port: 9000
c)创建一个类,添加 @GrpcService 注解(注入容器中,表示它是一个 proto 文件中描述的 service 的实现类),让他继承对应的 Base 接口,重写 proto 文件中提供 service 下的方法即可.
@GrpcService
public class HelloService extends HelloServiceGrpc.HelloServiceImplBase {
@Override
public void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {
String msg = request.getMsg();
System.out.println("收到客户端请求: " + msg);
HelloProto.HelloResponse response = HelloProto.HelloResponse
.newBuilder()
.setResult("ok!")
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
1.5、client 开发
a)pom.xml 文件如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.cyk</groupId>
<artifactId>api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
</dependencies>
b)配置文件如下:
spring:
application:
name: boot-client
grpc:
client:
grpc-server: # 自定义服务名
address: 'static://127.0.0.1:9000' # 调用 gRPC 的地址
negotiation-type: plaintext # 明文传输
c)通过 @GrpcClient 注解注入我们所需的代理对象(下面以阻塞式为例)
@RestController
public class HelloController {
@GrpcClient("grpc-server")
private HelloServiceGrpc.HelloServiceBlockingStub stub;
@RequestMapping("/hello")
public String hello(String msg) {
HelloProto.HelloRequest request = HelloProto.HelloRequest
.newBuilder()
.setMsg(msg)
.build();
HelloProto.HelloResponse response = stub.hello(request);
return response.getResult();
}
}
1.6、演示效果
二、gRPC 整合 SpringBoot 3.2.5
1.1、前置说明
与 gRPC 整合 SpringBoot 2.6.3 的主要差别如下:
- 客户端:@GrpcClient 注解貌似有点问题,注入一直空指针...(就先不用在 application.yml 中写官方的配置了) ,也就意味着 Stub 需要我们自己配置并注入这个 Bean 对象.
- 服务端:需要手动导入一些必要的自动配置类.
其他的无论是用法,还是依赖版本都是没有问题的.
1.2、父工程 pom.xml
<properties>
<kotlin.version>1.9.22</kotlin.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mockito-core.version>2.23.4</mockito-core.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
</dependency>
</dependencies>
<!--kotlin 相关构建-->
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>spring</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
1.3、proto 开发
syntax = "proto3";
option java_multiple_files = false;
option java_package = "org.cyk";
option java_outer_classname = "HelloProto";
service HelloService {
rpc hello(HelloRequest) returns(HelloResponse) {};
}
message HelloRequest {
string msg = 1;
}
message HelloResponse {
string result = 1;
}
1.4、server 开发
a)依赖如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.cyk</groupId>
<artifactId>kt-proto</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
</dependencies>
b)配置文件如下:
spring:
application:
name: boot-server
# 不启动 Tomcat 容器: server 这边只需要提供gRPC远程调用服务即可
# ,Tomcat 这种 Web 服务用不上启动反而浪费额外资源和端口
main:
web-application-type: none
# gRPC 启动端口
grpc:
server:
port: 9000
c)启动类如下:
import net.devh.boot.grpc.server.autoconfigure.*
import org.springframework.boot.autoconfigure.ImportAutoConfiguration
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
@ImportAutoConfiguration(
// GrpcCommonCodecAutoConfiguration::class, //gRPC 解码器,用于服务器和客户端通讯
// GrpcCommonTraceAutoConfiguration::class, //追踪功能,用于 gRPC 服务器跟踪,以便监视请求和响应的流程性能
// GrpcMetadataNacosConfiguration::class, //集成 Nacos
//一般来讲,以下两个自动配置类就够了
GrpcServerAutoConfiguration::class, //用于自动配置 gRPC 服务 ip 和 port
GrpcServerFactoryAutoConfiguration::class, //自动配置 gRPC 服务器工厂,根据需要创建 gRPC 服务器实例
// GrpcServerMetricAutoConfiguration::class, //集成度量指标, 允许在 gRPC 服务器端收集和暴露关于请求处理情况的指标。
// GrpcServerSecurityAutoConfiguration::class, //安全认证配置
// GrpcServerTraceAutoConfiguration::class // 用于集成分布式跟踪功能
)
class ServerApplication
fun main(args: Array<String>) {
runApplication<ServerApplication>(*args)
}
d) 服务如下:
import io.grpc.stub.StreamObserver
import net.devh.boot.grpc.server.service.GrpcService
import org.cyk.HelloProto
import org.cyk.HelloProto.HelloResponse
import org.cyk.HelloServiceGrpc
@GrpcService
class HelloService: HelloServiceGrpc.HelloServiceImplBase() {
override fun hello(request: HelloProto.HelloRequest, responseObserver: StreamObserver<HelloProto.HelloResponse>) {
val msg = request.msg
println("收到客户端请求: $msg")
val response = HelloResponse.newBuilder()
.setResult("$msg ok~")
.build()
responseObserver.onNext(response)
responseObserver.onCompleted()
}
}
1.5、client 开发
a)依赖如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.cyk</groupId>
<artifactId>kt-proto</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.14.0.RELEASE</version>
</dependency>
</dependencies>
b)配置文件如下:
server:
port: 9001
spring:
application:
name: boot-client
# 自定义 gRPC 配置
grpc-client:
host: '127.0.0.1'
port: 9000
c)自动配置类如下:
import io.grpc.ManagedChannelBuilder
import org.cyk.HelloServiceGrpc
import org.cyk.HelloServiceGrpc.HelloServiceBlockingStub
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class GrpcConfig {
@Value("\${grpc-client.host}")
private var host: String = ""
@Value("\${grpc-client.port}")
private var port: Int = -1
@Bean
fun helloServiceBlockingStub(): HelloServiceBlockingStub {
val managedChannel = ManagedChannelBuilder
.forAddress(host, port)
.usePlaintext()
.build()
return HelloServiceGrpc.newBlockingStub(managedChannel)
}
}
d)启动类如下:
@SpringBootApplication
class ClientApplication
fun main(args: Array<String>) {
runApplication<ClientApplication>(*args)
}
e) 服务如下:
import org.cyk.HelloProto.HelloRequest
import org.cyk.HelloServiceGrpc.HelloServiceBlockingStub
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/hello")
class HelloApi(
val stub: HelloServiceBlockingStub
) {
@GetMapping
fun hello(msg: String): String {
val request = HelloRequest.newBuilder()
.setMsg(msg)
.build()
val response = stub.hello(request)
return response.result
}
}
1.6、演示效果
三、gRPC 统一异常处理
3.1、情景
a)背景:有一个 clientA 和 serverA. 此时 clientA 调用 serverA,但是 serverA 中引发了异常,并且无论是什么异常,反馈到 clientA 这边都是一个相同的异常 "io.grpc.StatusRuntimeException",错误描述为:"UNKNOWN".
而我的需求是,无论 serverA 这边报什么错,都需要把对应的错误描述交给 clientA.
b)解决方法:这里实际上也有很多种办法,例如给 serverA 中每个实现的 grpc 方法都进行 try catch,然后通过 onError 传递异常信息,但是这样做太繁琐了.
因此这里我给出一种统一的解决办法.
3.2、异常统一处理
a)clientA 处理:由于 serverA 这边返回的异常统一为 StatusRuntimeException,因此在 clientA 中的统一异常处理就捕获该异常即可.
/**
* 参数异常相关
*/
@ExceptionHandler(
MethodArgumentNotValidException::class,
ConstraintViolationException::class,
MissingRequestValueException::class,
MethodArgumentTypeMismatchException::class,
IllegalArgumentException::class,
StatusRuntimeException::class, //grpc 异常
)
fun handlerParamException(ex: Exception): ApiResp<*> {
ex.printStackTrace()
log.error(ex.message)
return ApiResp.no(ApiStatus.INVALID_PARAM)
}
b)serverA 处理:添加以下配置
import io.grpc.*
import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor
@GrpcGlobalServerInterceptor
class GlobalGrpcExceptionHandler : ServerInterceptor {
override fun <ReqT : Any?, RespT : Any?> interceptCall(
p0: ServerCall<ReqT, RespT>,
p1: io.grpc.Metadata,
p2: ServerCallHandler<ReqT, RespT>,
): ServerCall.Listener<ReqT> {
val delegate = p2.startCall(p0, p1)
return object : ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(delegate) {
override fun onHalfClose() {
try {
super.onHalfClose()
} catch (e: Exception) {
p0.close(
Status.INTERNAL.withCause(e).withDescription(e.message),
Metadata()
)
}
}
}
}
}
c)这样将来就可以收到你自定义的异常信息了~