1. 概述
gRPC 是 Google 开发的高性能开源 RPC 框架。它能有效消除样板代码,连接数据中心内外的多语言服务。其 API 基于 Protocol Buffers,通过 protoc 编译器为多种支持的语言生成代码。
我们可以将 gRPC 视为 REST、SOAP 或 GraphQL 的替代方案,它构建在 HTTP/2 之上,利用了多路复用和流式连接等特性。
本教程将学习如何在 Spring Boot 中实现 gRPC 服务提供者和消费者。
2. 挑战
首先要注意 Spring Boot 并不直接支持 gRPC。虽然支持 Protocol Buffers(可用于实现基于 protobuf 的 REST 服务),但我们需要通过第三方库或自行解决以下挑战:
- 平台依赖的编译器:protoc 编译器与平台相关。若在构建时生成存根,会使构建过程更复杂且易出错。
- 依赖冲突:需在 Spring Boot 应用中引入兼容的依赖。Java 版的 protoc 会添加 javax.annotation.Generated 注解,迫使我们添加旧版 Java EE Annotations for Java 库。
- 服务器运行时:gRPC 服务提供者需运行在服务器中。gRPC for Java 提供shaded Netty,需将其集成到 Spring Boot 或替换 Spring Boot 自带的服务器。
- 消息传输:Spring Boot 提供的客户端(如阻塞式 RestClient 或非阻塞式 WebClient)无法用于 gRPC,因为 gRPC 对阻塞/非阻塞调用都采用自定义传输技术。
- 配置复杂性:gRPC 使用自有技术栈,需通过 Spring Boot 风格的配置属性管理。
3. 示例项目
幸运的是,有第三方 Spring Boot Starter 可帮我们解决这些挑战,例如 LogNet 或 grpc ecosystem project。后者同时支持提供者和消费者,且具备更多集成特性,因此我们选用它。
本示例中,我们设计一个简单的 HelloWorld API,包含单个 Proto 文件:
syntax = "proto3";
option java_package = "com.baeldung.helloworld.stubs";
option java_multiple_files = true;
message HelloWorldRequest {
// 要问候的名字,默认为 "World"
optional string name = 1;
}
message HelloWorldResponse {
string greeting = 1;
}
service HelloWorldService {
rpc SayHello(stream HelloWorldRequest) returns (stream HelloWorldResponse);
}
这里使用了双向流式通信特性。
3.1. gRPC 存根生成
由于提供者和消费者使用相同存根,我们将其放在独立的非 Spring 项目中生成。这样能隔离 protoc 编译器配置和 Java EE 注解依赖,避免与 Spring Boot 项目生命周期耦合。
3.2. 服务提供者
实现服务提供者非常简单。首先添加 starter 和存根项目依赖:
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.15.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.baeldung.spring-boot-modules</groupId>
<artifactId>helloworld-grpc-java</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
无需引入 Spring MVC 或 WebFlux,因为 starter 已包含 shaded Netty 服务器。可在 application.yml 中配置服务器端口:
grpc:
server:
port: 9090
然后实现服务类并用 @GrpcService 注解:
@GrpcService
public class HelloWorldController extends HelloWorldServiceGrpc.HelloWorldServiceImplBase {
@Override
public StreamObserver<HelloWorldRequest> sayHello(
StreamObserver<HelloWorldResponse> responseObserver
) {
// ...
}
}
3.3. 服务消费者
消费者需添加 starter 和存根依赖:
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.15.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.baeldung.spring-boot-modules</groupId>
<artifactId>helloworld-grpc-java</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
在 application.yml 中配置服务连接:
grpc:
client:
hello:
address: localhost:9090
negotiation-type: plaintext
名称 "hello" 是自定义标识符。通过它可配置多个连接,并在注入 gRPC 客户端时引用:
@GrpcClient("hello")
HelloWorldServiceGrpc.HelloWorldServiceStub stub;
4. 踩坑指南
虽然 Spring Boot 集成 gRPC 很简单,但需注意以下陷阱:
4.1. SSL 握手问题
HTTP 传输默认不加密,除非使用 SSL。集成的 Netty 服务器默认不启用 SSL,需手动配置。
本地测试时可使用明文连接,此时需配置消费者:
grpc:
client:
hello:
negotiation-type: plaintext
⚠️ 关键点:消费者默认使用 TLS,而提供者默认跳过 SSL 加密,两者默认配置不匹配。
4.2. 消费者注入限制
通过字段注入客户端:
@GrpcClient("hello")
HelloWorldServiceGrpc.HelloWorldServiceStub stub;
此机制基于 *BeanPostProcessor*,是 Spring 依赖注入的补充。因此 @GrpcClient 不能与 @Autowired 或构造器注入混用,只能用于字段注入。
可通过配置类解耦注入逻辑:
@Configuration
public class HelloWorldGrpcClientConfiguration {
@GrpcClient("hello")
HelloWorldServiceGrpc.HelloWorldServiceStub helloWorldClient;
@Bean
MyHelloWorldClient helloWorldClient() {
return new MyHelloWorldClient(helloWorldClient);
}
}
4.3. 传输对象映射陷阱
protoc 生成的数据类型在调用 setter 时可能因 null 值失败:
public HelloWorldResponse map(HelloWorldMessage message) {
return HelloWorldResponse
.newBuilder()
.setGreeting( message.getGreeting() ) // 可能为 null
.build();
}
✅ 解决方案:调用 setter 前必须进行 null 检查。使用映射框架时需特殊配置,例如 MapStruct 需添加:
@Mapper(
componentModel = "spring",
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS
)
public interface HelloWorldMapper {
HelloWorldResponse map(HelloWorldMessage message);
}
4.4. 测试支持不足
starter 未提供特殊测试支持。gRPC for Java 项目仅支持 JUnit 4,且不支持 JUnit 5。
4.5. 原生镜像兼容性
构建原生镜像时,当前不支持 gRPC。由于客户端注入依赖反射,**必须添加额外配置**才能运行。
5. 总结
本文展示了在 Spring Boot 中实现 gRPC 提供者和消费者的方法。需注意其局限性,如测试支持和原生镜像兼容性不足。
所有代码实现可在 GitHub 获取。