gRPC - gRPC 整合 SpringBoot2.x、3.x(全代码、统一异常处理)

本文详细介绍了gRPC如何与SpringBoot2.6.3和3.2.5版本集成,包括创建项目、配置、api开发、服务器和客户端实现,以及统一异常处理的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、gRPC 整合 SpringBoot 2.6.3

1.1、创建项目

1.2、前置配置

1.3、api 开发

1.4、server 开发

1.5、client 开发

1.6、演示效果

二、gRPC 整合 SpringBoot 3.2.5

1.1、前置说明

1.2、父工程 pom.xml

1.3、proto 开发

1.4、server 开发

1.5、client 开发

1.6、演示效果

三、gRPC 统一异常处理

3.1、情景

3.2、异常统一处理


一、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)这样将来就可以收到你自定义的异常信息了~

Java + gRPC + grpc-gateway 的实践主要分为以下几个步骤: 1. 定义 proto 文件 在 proto 文件中定义需要调用的服务以及方法,同时指定请求和响应的数据类型。例如: ``` syntax = "proto3"; package example; service ExampleService { rpc ExampleMethod (ExampleRequest) returns (ExampleResponse) {} } message ExampleRequest { string example_field = 1; } message ExampleResponse { string example_field = 1; } ``` 2. 使用 protoc 编译 proto 文件 使用 protoc 编译 proto 文件,生成 Java 代码。例如: ``` protoc --java_out=./src/main/java ./example.proto ``` 3. 实现 gRPC 服务 在 Java 代码中实现定义的 gRPC 服务,例如: ``` public class ExampleServiceImpl extends ExampleServiceGrpc.ExampleServiceImplBase { @Override public void exampleMethod(ExampleRequest request, StreamObserver<ExampleResponse> responseObserver) { // 实现具体逻辑 ExampleResponse response = ExampleResponse.newBuilder().setExampleField("example").build(); responseObserver.onNext(response); responseObserver.onCompleted(); } } ``` 4. 启动 gRPC 服务器 使用 gRPC 提供的 ServerBuilder 构建 gRPC 服务器,并启动服务器。例如: ``` Server server = ServerBuilder.forPort(8080).addService(new ExampleServiceImpl()).build(); server.start(); ``` 5. 集成 grpc-gateway 使用 grpc-gateway 可以将 gRPC 服务转换为 HTTP/JSON API。在 proto 文件中添加以下内容: ``` import "google/api/annotations.proto"; service ExampleService { rpc ExampleMethod (ExampleRequest) returns (ExampleResponse) { option (google.api.http) = { post: "/example" body: "*" }; } } ``` 在 Java 代码中添加以下内容: ``` Server httpServer = ServerBuilder.forPort(8081).addService(new ExampleServiceImpl()).build(); httpServer.start(); String grpcServerUrl = "localhost:8080"; String httpServerUrl = "localhost:8081"; ProxyServerConfig proxyConfig = new ProxyServerConfig(grpcServerUrl, httpServerUrl, "/example"); HttpProxyServer httpProxyServer = new HttpProxyServer(proxyConfig); httpProxyServer.start(); ``` 6. 测试 使用 HTTP/JSON API 调用 gRPC 服务,例如: ``` POST http://localhost:8081/example Content-Type: application/json { "example_field": "example" } ``` 以上就是 Java + gRPC + grpc-gateway 的实践步骤。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈亦康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值