本文是 Spring Boot 4 系列第 15 篇 | 基于 Spring Boot 4.1.0 仓库(
module/spring-boot-grpc-*)与官方文档io/grpc.adoc| 预计阅读 25 分钟文末附「完整链路图」与「四个核心设计思想」,从社区 Starter 迁移时可直接对照。
写在前面
微服务内部调用,JSON over HTTP 有两个老问题:一是 JSON 文本体积大,{"name":"zhangsan","age":18} 里键名和引号占了近一半流量;二是跨语言契约难维护,字段命名、类型映射全靠两边对着文档对齐。
gRPC 是业界公认的解法:.proto 文件定义契约,Protocol Buffers 二进制编码体积小一个量级,HTTP/2 多路复用省掉连接开销。但 Java 服务用 gRPC 一直有个别扭的地方------没有官方支持 。要么用 yidongnan 的社区 grpc-spring-boot-starter(维护靠个人、版本跟着社区走),要么自己手写 ServerBuilder 装配。
Spring Boot 4.1 补上了这块。官方文档(io/grpc.adoc)开篇第一句:
"Spring Boot include support for developing and testing both client and server gRPC applications."
而且它没有再造一个轮子,而是建立在 Spring 官方新项目 Spring gRPC (org.springframework.grpc:spring-grpc-core,1.1.0)之上:Boot 负责自动配置,Spring gRPC 负责核心抽象------与 Boot 和 Spring Data、Spring Security 的关系一致。本文从依赖到测试把这套架构逐层拆开。
内容速览
- 全景图:三层结构(应用/自动配置/Spring gRPC 核心)+ 三条 Transport
- 三个模块 + 四个 Starter,Server Starter 默认带 gRPC Reflection 服务
- 服务端契约是 BindableService Bean,@GrpcService 只是便捷注解
- 三个条件触发服务端自动配置:没有服务就不起服务器
- 三种服务器形态:Netty(默认)/ Servlet / InProcess 的条件分派
- 纠偏:Boot 4.1 没有 @GrpcClient,官方模型是 @ImportGrpcClients 导入 stub
- 命名通道:一个通道对应一个远端服务,多个 stub 可共享
- @AutoConfigureTestGrpcTransport 零配置进程内测试,与真端口随机测试两条路
- 官方 vs 社区 Starter 对照表与迁移建议
- 六个边界与坑:配置前缀别混、Transport 互斥、TLS 走 SslBundle
一、全景图:三层结构 + 三条 Transport
java
┌─────────────────────────────────────────────────────────┐
│ 应用层(你写的代码) │
│ Server: @GrpcService 实现类(继承 .proto 生成的 ImplBase) │
│ Client: @ImportGrpcClients 导入 stub + 直接注入 stub Bean │
│ Test: @AutoConfigureTestGrpcTransport / @LocalGrpcServerPort
└─────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────┐
│ Boot 4.1 自动配置层(module/spring-boot-grpc-*) │
│ GrpcServerAutoConfiguration(spring.grpc.server.*) │
│ GrpcClientAutoConfiguration(spring.grpc.client.*) │
│ TestGrpcTransportAutoConfiguration(spring-boot-grpc-test)│
└─────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────┐
│ Spring gRPC 1.1.0 核心层(org.springframework.grpc) │
│ GrpcServerFactory / GrpcChannelFactory / GrpcServiceDiscoverer
│ GrpcServerLifecycle / GrpcService / ImportGrpcClients │
└─────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────┐
│ Transport(gRPC Java 1.80.0) │
│ Netty(默认,端口 9090)/ Netty Shaded / Servlet(web 容器)
│ InProcess(进程内,测试与同进程通信) │
└─────────────────────────────────────────────────────────┘
依赖坐标(platform/spring-boot-dependencies/build.gradle 实证):
| 库 | 版本 | 说明 |
|---|---|---|
gRPC Java(grpc-bom) |
1.80.0 | Netty/Stub/InProcess/Servlet 等全部 io.grpc 构件 |
Spring gRPC(spring-grpc-core) |
1.1.0 | 核心抽象:工厂、服务发现、生命周期、注解 |
二、第一层:模块与 Starter------官方支持长什么样
2.1 三个模块 + 四个 Starter
Spring Boot 4.1.0 仓库里有三个 gRPC 模块:
arduino
module/spring-boot-grpc-server # Server 自动配置
module/spring-boot-grpc-client # Client 自动配置
module/spring-boot-grpc-test # 测试支持(In-Process Transport)
四个 Starter POM(starter/ 目录):
| Starter | 依赖(build.gradle 实证) |
|---|---|
spring-boot-starter-grpc-server |
spring-boot-starter + spring-boot-grpc-server + grpc-netty + grpc-services |
spring-boot-starter-grpc-client |
spring-boot-starter + spring-boot-grpc-client + grpc-netty + grpc-stub |
spring-boot-starter-grpc-server-test |
面向 server 测试 |
spring-boot-starter-grpc-client-test |
面向 client 测试 |
两个细节:
- Server Starter 默认带上
grpc-services------官方文档说的"gRPC Reflection 服务"(让 grpcurl 等客户端在线浏览服务元数据、下载.proto)默认可用,不需要额外加依赖; - Netty 是默认 Transport :Starter 直接引入
grpc-netty,"零配置起服务"背后是 Netty 服务器在监听。
仓库里还有 8 个 smoke-test(smoke-test/spring-boot-smoke-test-grpc-*:server、client、secure、oauth、servlet、netty-shaded、test......),官方用它们验证每种形态都能跑通,可以直接当作官方用法示例集。
三、第二层:Server 自动配置------从 @GrpcService 到监听端口
3.1 服务端开发模型:@GrpcService + ImplBase
先看服务端怎么写。.proto 文件经 protobuf 插件生成 HelloWorldGrpc 基类,实现类继承它并标上 Spring gRPC 的 @GrpcService 注解(org.springframework.grpc.server.service.GrpcService),交给组件扫描即可。仓库 smoke-test 的 HelloWorldService(smoke-test/spring-boot-smoke-test-grpc-server)就是标准写法:
java
@GrpcService
public class HelloWorldService extends HelloWorldGrpc.HelloWorldImplBase {
private static Log logger = LogFactory.getLog(HelloWorldService.class);
@Override
public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
String name = request.getName();
logger.info("sayHello " + name);
Assert.isTrue(!name.startsWith("error"), () -> "Bad name: " + name);
Assert.state(!name.startsWith("internal"), "Internal error");
String message = "Hello '%s'".formatted(name);
HelloReply reply = HelloReply.newBuilder().setMessage(message).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
官方文档的原话定义了契约:
"Spring gRPC will automatically expose any bean that implements
io.grpc.BindableServiceas a gRPC server."
也就是说:只要是 BindableService 类型的 Bean,就会被自动发布成 gRPC 服务 ------生成的 stub 基类天然实现 BindableService,@GrpcService 只是让普通实现类变成 Bean 的便捷注解。反编译 spring-grpc-core 1.1.0 的字节码可以看到它的元注解(@Target 是 TYPE + METHOD):
java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Service // 类上:@Service 语义,组件扫描即可发现
@Bean // 方法上:配合 @Bean 工厂方法使用
public @interface GrpcService {
Class<? extends ServerInterceptor>[] interceptors() default {};
}
两个细节:类级用法靠 @Service 让组件扫描发现;方法级用法(@Target 里的 METHOD)靠 @Bean 支持"在一个配置类里用 @Bean 方法声明服务";interceptors() 属性还可以给单个服务指定专属拦截器。
3.2 GrpcServerAutoConfiguration:三个条件触发
module/spring-boot-grpc-server/.../autoconfigure/GrpcServerAutoConfiguration.java(@since 4.1.0):
java
@AutoConfiguration
@ConditionalOnClass({ GrpcServerFactory.class, Grpc.class })
@ConditionalOnBean(BindableService.class)
@ConditionalOnBooleanProperty(name = "spring.grpc.server.enabled", matchIfMissing = true)
@EnableConfigurationProperties(GrpcServerProperties.class)
@Import({ GrpcServerCodecConfiguration.class, ServletGrpcServerConfiguration.class,
ShadedNettyGrpcServerConfiguration.class, NettyGrpcServerConfiguration.class,
InProcessGrpcServerConfiguration.class })
public final class GrpcServerAutoConfiguration {
// ...
}
三个条件:
@ConditionalOnClass({ GrpcServerFactory.class, Grpc.class }):classpath 上有 Spring gRPC 核心类 + gRPC 本体(Starter 已经满足);@ConditionalOnBean(BindableService.class):容器里至少有一个 gRPC 服务实现------没有服务就不起服务器,这是"无服务零开销"的关键;@ConditionalOnBooleanProperty("spring.grpc.server.enabled", matchIfMissing = true):默认开启,spring.grpc.server.enabled=false可关。
@Import 一次性把四种服务器形态的配置类全部拉进来,由各自的条件决定谁生效(见 3.4)。
3.3 GrpcServerProperties:spring.grpc.server.*
属性前缀是 spring.grpc.server (GrpcServerProperties,@ConfigurationProperties("spring.grpc.server"))。注意一个流传说法要纠偏:社区 Starter 用的是 grpc.server.port,官方 Boot 4.1 的配置是 spring.grpc.server.port。主要子项(源码字段实证):
| 属性 | 默认值 | 说明 |
|---|---|---|
spring.grpc.server.port |
9090(Netty 默认) | 监听端口,0 表示绑定动态端口(测试常用) |
spring.grpc.server.address |
--- | 绑定地址 |
spring.grpc.server.shutdown.grace-period |
30s | 优雅停机宽限期 |
spring.grpc.server.inbound.message.max-size |
4MB | 入站消息上限 |
spring.grpc.server.inbound.metadata.max-size |
8KB | 元数据上限 |
spring.grpc.server.inprocess.name |
--- | 设置后启用 In-Process 服务器 |
spring.grpc.server.keepalive.time |
2h | keepalive 间隔 |
spring.grpc.server.keepalive.timeout |
20s | keepalive 超时 |
spring.grpc.server.ssl.* |
关闭 | TLS(bundle 引用 spring.ssl.bundle.jks) |
3.4 三种服务器:Netty / Servlet / InProcess 的条件分派
@Import 拉进来的三个工厂配置类,靠条件互斥:
① NettyGrpcServerConfiguration(默认):
java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(NettyServerBuilder.class)
@ConditionalOnMissingNetworkGrpcServer
@ConditionalOnGrpcServerFactoryEnabled
class NettyGrpcServerConfiguration {
@Bean
NettyGrpcServerFactory nettyGrpcServerFactory(GrpcServerProperties properties,
GrpcServiceDiscoverer serviceDiscoverer, GrpcServiceConfigurer serviceConfigurer,
GrpcServerBuilderCustomizers grpcServerBuilderCustomizers, SslBundles bundles,
ObjectProvider<GrpcServerFactoryCustomizer> customizers) {
NettyAddress address = NettyAddress.fromProperties(properties);
ServerCredentials credentials = ServerCredentials.get(properties.getSsl(), bundles, ...);
NettyGrpcServerFactory factory = new NettyGrpcServerFactory(address.toString(), ...);
customizers.orderedStream().forEach((customizer) -> customizer.customize(factory));
serviceDiscoverer.findServices()
.stream()
.map((spec) -> serviceConfigurer.configure(spec, factory))
.forEach(factory::addService);
return factory;
}
@Bean
@ConditionalOnMissingBean(name = "nettyGrpcServerLifecycle")
GrpcServerLifecycle nettyGrpcServerLifecycle(NettyGrpcServerFactory factory, GrpcServerProperties properties,
ApplicationEventPublisher eventPublisher) {
return new GrpcServerLifecycle(factory, properties.getShutdown().getGracePeriod(), eventPublisher);
}
}
三个关键抽象都来自 Spring gRPC 核心:
GrpcServiceDiscoverer:扫描容器里所有BindableServiceBean,得到服务清单(默认实现DefaultGrpcServiceDiscoverer);GrpcServiceConfigurer:把每个服务配置到指定工厂(默认实现DefaultGrpcServiceConfigurer,负责拦截器等织入);GrpcServerLifecycle:把工厂包装成 SpringLifecycle,跟随容器 start/stop,停机时应用shutdown.grace-period优雅退出。
② ServletGrpcServerConfiguration :把 gRPC 跑在现有 Web 容器里(Tomcat/Jetty 等),条件是 @ConditionalOnWebApplication(type = SERVLET) + classpath 有 GrpcServlet(grpc-servlet-jakarta)+ spring.grpc.server.servlet.enabled(默认 true)。此时通过 GrpcServletRegistration(一个 DynamicRegistrationBean)把 gRPC 的 Servlet 注册进 Web 容器,与你的 HTTP 接口共用端口 ------注意 Netty 与 Servlet 互斥(@ConditionalOnMissingNetworkGrpcServer)。
③ InProcessGrpcServerConfiguration :设置 spring.grpc.server.inprocess.name 后启用,创建 InProcessGrpcServerFactory------服务器在进程内、不监听任何网络端口,客户端用 in-process:<name> 作为 target 就能连上(测试和同进程通信用)。
3.5 附加能力:异常处理、健康、安全、可观测
@GrpcAdvice异常处理 :GrpcServerAutoConfiguration里有一个GrpcAdviceConfiguration(@ConditionalOnBean(annotation = GrpcAdvice.class)),与 Spring MVC 的@ControllerAdvice同构------用@GrpcAdvice标注的类里的@GrpcExceptionHandler方法会把业务异常映射成 gRPC Status;默认还有一个GrpcExceptionHandlerInterceptor(@GlobalServerInterceptor)兜底;- 健康检查 :
spring-boot-grpc-server的health包(GrpcServerHealth、StatusMapper等)与spring-boot-health模块联动,gRPC 健康检查协议(grpc-health-probe)可用; - 安全 :
spring-boot-security/spring-boot-security-oauth2-resource-server是 optional 依赖,仓库里有spring-boot-smoke-test-grpc-server-secure、-oauth两个 smoke-test 验证; - 可观测性 :
GrpcServerObservationAutoConfiguration(optional),gRPC 调用自动接入 Micrometer Observation,与系列第十三篇的可观测性体系打通。
四、第三层:Client 自动配置------没有 @GrpcClient,只有 @ImportGrpcClients
4.1 先说结论:Boot 4.1 没有 @GrpcClient
很多迁移文章会写"官方支持 @GrpcClient 注解"------这是错的 。@GrpcClient 是社区 Starter(yidongnan/grpc-spring-boot-starter)的 API,Spring Boot 4.1.0 全仓库搜不到这个注解。官方客户端模型是:@ImportGrpcClients 导入 stub 类 → stub 变成普通 Bean 直接注入。
看仓库 smoke-test 的 SampleGrpcClientApplication(smoke-test/spring-boot-smoke-test-grpc-client):
java
@SpringBootApplication
@ImportGrpcClients(types = HelloWorldBlockingStub.class)
public class SampleGrpcClientApplication {
@Bean
ApplicationRunner applicationRunner(HelloWorldBlockingStub hello) {
return (args) -> {
HelloRequest request = HelloRequest.newBuilder().setName("Spring").build();
HelloReply reply = hello.sayHello(request);
System.out.println(">>> " + reply.getMessage());
};
}
}
@ImportGrpcClients(org.springframework.grpc.client.ImportGrpcClients)把生成的 HelloWorldBlockingStub 注册成 Bean,构造函数里注入即用。官方文档的说明:
"Each import includes a
targetwhich can either be a logical channel name, or the base URL of the remote server. We typically recommend using channel names rather than hard-coding targets. ... If you don't specify atargetthen 'default' is used."
即:不指定 target 就用名为 default 的通道。这与社区 Starter 的"每个 stub 一个地址"模型很不一样------官方把通道作为一等概念,一个通道对应一个远端服务,多个 stub 可以共享。
4.2 GrpcClientAutoConfiguration:命名通道 + 条件
module/spring-boot-grpc-client/.../autoconfigure/GrpcClientAutoConfiguration.java(@since 4.1.0):
java
@AutoConfiguration(before = CompositeChannelFactoryAutoConfiguration.class)
@ConditionalOnClass({ AbstractStub.class, GrpcChannelBuilderCustomizer.class })
@ConditionalOnProperty(name = "spring.grpc.client.enabled", matchIfMissing = true)
@EnableConfigurationProperties(GrpcClientProperties.class)
@Import({ GrpcClientCodecConfiguration.class, ShadedNettyGrpcClientConfiguration.class,
NettyGrpcClientConfiguration.class, InProcessGrpcClientConfiguration.class })
public final class GrpcClientAutoConfiguration {
// ...
}
与 Server 侧对称:默认开启(spring.grpc.client.enabled)、@Import 拉入四种 Transport 配置(Netty/ShadedNetty/InProcess 互斥或叠加)。它注册的 Bean 里有两个关键角色:
GrpcChannelBuilderCustomizers:把属性集中应用到每个 Channel 的 builder 上(keepalive、消息上限、压缩器等);PropertiesChannelCredentialsProvider:从spring.grpc.client.channel.<name>.ssl.*与SslBundles提供 TLS 凭证。
4.3 GrpcClientProperties:命名通道怎么配
属性前缀 spring.grpc.client ,核心结构是命名通道 Map(源码字段实证):
java
@ConfigurationProperties("spring.grpc.client")
public class GrpcClientProperties {
private final Map<String, Channel> channel = new LinkedHashMap<>();
// Channel: target / userAgent / bypassCertificateValidation / serviceConfig
// inbound(message.maxSize=4MB, metadata.maxSize=8KB)
// default(deadline, loadBalancingPolicy="round_robin")
// idle.timeout=20s / keepalive(time=5m, timeout=20s) / ssl / health
}
每个通道的 target 默认是 static://localhost:9090(与 Server 默认端口 9090 对应)。配置一个命名通道:
yaml
spring:
grpc:
client:
channel:
myservice:
target: static://grpc.example.com:9090
keepalive:
timeout: 40s
inbound:
message:
max-size: 8MB
(属性路径以 GrpcClientProperties 的类结构为准:keepalive 是 Channel 层的分组,不是 inbound 的子项。)
于是客户端从"注入 stub"到"连上服务"的完整链路是:
python
@ImportGrpcClients(types = HelloWorldBlockingStub.class)
→ 注册 stub Bean(绑定通道名 "default" 或指定的名字)
→ GrpcChannelFactory 按通道名创建 Channel(Netty,应用属性定制)
→ stub 注入业务 Bean,sayHello() 调用通过 Channel 发出
4.4 客户端三种 Transport 与 TLS
- Netty(默认,Starter 已带);
- Shaded Netty :与 Server 侧对应,Netty 与别的库冲突时切换
grpc-netty-shaded; - In-Process :classpath 有
grpc-inprocess时可用,target 写in-process:<name>,配合 3.4 的 In-Process 服务器做同进程通信; - TLS :
spring.grpc.client.channel.<name>.ssl.*+spring.ssl.bundle.jks的 SslBundle 体系,支持单向与双向(mTLS)。
五、第四层:测试支持------In-Process Transport 与随机端口
5.1 @AutoConfigureTestGrpcTransport:零配置的进程内测试
module/spring-boot-grpc-test 是专门为测试准备的模块(spring-boot-starter-grpc-client-test / -server-test 对应两个 Starter)。核心是 @AutoConfigureTestGrpcTransport 注解(org.springframework.boot.grpc.test.autoconfigure)和它背后的 TestGrpcTransportAutoConfiguration。官方文档原文:
"The
@AutoConfigureTestGrpcTransportannotation allows you to quickly replace gRPC communication channels with in-process channels specifically designed for testing. Unlike regular in-process channels, these test channels do not require any configuration."
它做的事情(TestGrpcTransportAutoConfiguration 源码实证):
java
@AutoConfiguration(before = { GrpcServerAutoConfiguration.class, GrpcClientAutoConfiguration.class })
@ConditionalOnClass({ InProcessServerBuilder.class, InProcessGrpcServerFactory.class })
public final class TestGrpcTransportAutoConfiguration {
private static final String address = InProcessServerBuilder.generateName();
// ...
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
TestGrpcServerFactory testGrpcServerFactory(GrpcServiceDiscoverer serviceDiscoverer, ...) { ... }
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
TestGrpcChannelFactory testGrpcChannelFactory(ClientInterceptorsConfigurer interceptorsConfigurer) { ... }
}
三个要点:
InProcessServerBuilder.generateName()生成共享地址 ------Server 工厂和 Channel 工厂共用同一个进程内地址,两端自动对上,不需要配置任何 target;@Order(HIGHEST_PRECEDENCE)的工厂 Bean 顶替自动配置 (它声明在GrpcServerAutoConfiguration/GrpcClientAutoConfiguration之前执行),所以网络工厂不会启动、不占端口;- 测试在进程内跑,速度快,且不会误连真实服务。
5.2 @LocalGrpcServerPort:要真端口也可以
如果测试想用真实网络连接(比如验证与真实客户端的兼容性),官方推荐随机端口:spring.grpc.server.port=0,再用 @LocalGrpcServerPort 注解注入实际端口。仓库 smoke-test 的做法(SampleGrpcServerApplicationTests):
java
@SpringBootTest(properties = "spring.grpc.server.port=0")
class SampleGrpcServerApplicationTests {
// ...
}
官方文档原文:
"To start a gRPC server using a random port, set
spring.grpc.server.portto0. You can use the@LocalGrpcServerPortannotation to obtain the actual port that the server started on."
六、官方方案 vs 社区 Starter:一张表看清差异
| 维度 | Boot 4.1 官方(spring-grpc) | 社区 grpc-spring-boot-starter |
|---|---|---|
| 服务端注解 | @GrpcService(org.springframework.grpc.server.service) |
@GrpcService(net.devh.boot.grpc.server) |
| 客户端注解 | @ImportGrpcClients(types = ...) 导入 stub |
@GrpcClient("name") 注入 stub |
| 通道模型 | 命名通道(spring.grpc.client.channel.<name>.*),默认通道 default |
每个 @GrpcClient 独立配置 |
| Server 配置 | spring.grpc.server.port 等 |
grpc.server.port 等 |
| 测试支持 | 官方 spring-boot-grpc-test + @AutoConfigureTestGrpcTransport |
依赖 GrpcCleanupRule 等手动组装 |
| 维护方 | Spring 官方(Spring gRPC 项目,Boot 管理版本) | 社区维护 |
| 底层 | Spring gRPC 1.1.0 + gRPC Java 1.80.0(Boot BOM 统一管理) | 随社区版本 |
迁移建议 :从社区 Starter 迁到官方,服务端代码几乎不动(都是继承生成的 ImplBase),主要改三处:@GrpcService 的 import、@GrpcClient → @ImportGrpcClients + stub Bean 注入、配置前缀 grpc.* → spring.grpc.*。
七、边界与坑
- 服务发现只认 Bean,不认包扫描 :官方文档说得直白------"any bean that implements
BindableService"。实现类必须真的是 Bean(@GrpcService或@Component都行),只是"有.proto生成类"不会自动发布; - 没有服务就不起服务器 :
@ConditionalOnBean(BindableService.class)意味着一个只有客户端、没有服务实现的 jar 即使带上 server starter 也不会监听端口------这是特性不是 bug,但排查"服务器没起来"时要先想到它; grpc.server.*与spring.grpc.server.*别混:社区与官方的配置前缀不同,迁移时 IDE 的配置提示(来自 Boot 的配置元数据)是最可靠的对照;- In-Process 服务器不是测试专属 :
spring.grpc.server.inprocess.name设了就会启动进程内服务器,in-process:<name>可以跨应用上下文使用;@AutoConfigureTestGrpcTransport才是"为测试特制、零配置"的通道; - Servlet 模式与 Netty 互斥 :
@ConditionalOnMissingNetworkGrpcServer保证同一时刻只有一个网络服务器工厂,想切换模式就切换 classpath(grpc-nettyvsgrpc-servlet-jakarta),不要两个都配; - TLS 走
spring.ssl.bundle.*:官方方案复用 Boot 4 的 SslBundle 体系(spring.ssl.bundle.jks定义证书包,spring.grpc.server.ssl.bundle/spring.grpc.client.channel.<name>.ssl.bundle引用),与 HTTP 客户端共用同一套证书管理。
八、总结
用一张图回顾:
sql
.proto 文件
→ protobuf 插件生成 HelloWorldGrpc / stub / 消息类
→ Server:@GrpcService 实现类(BindableService Bean)
→ GrpcServerAutoConfiguration(spring.grpc.server.*)
→ GrpcServiceDiscoverer 收集服务 → NettyGrpcServerFactory(端口 9090)
→ GrpcServerLifecycle 随容器启停(grace-period 优雅停机)
→ Client:@ImportGrpcClients 导入 stub
→ GrpcClientAutoConfiguration(spring.grpc.client.channel.<name>.*)
→ GrpcChannelFactory 建 Channel → stub 直接注入使用
→ Test:@AutoConfigureTestGrpcTransport(进程内,零配置)
或 spring.grpc.server.port=0 + @LocalGrpcServerPort
四个核心设计思想:
- 框架分层复用:Boot 不实现 gRPC,而是自动配置 Spring gRPC(官方新项目)------与 Spring Data/Security 的模式一致,职责清晰;
- 条件装配的"按需启动" :
@ConditionalOnBean(BindableService.class)让"没有服务不监听端口",@ConditionalOnMissingNetworkGrpcServer让 Transport 互斥------自动配置的条件是行为契约,不只是依赖声明; - 通道(Channel)是一等概念:客户端模型从社区的"注解标记"进化到"命名通道 + stub Bean",一个通道对应一个服务,配置集中、可共享、可替换(测试时整个通道层被替换成 In-Process);
- 测试基础设施与生产同构 :
@AutoConfigureTestGrpcTransport用@Order(HIGHEST_PRECEDENCE)的工厂 Bean 顶替生产工厂------底层就是优先级最高的 Bean 替换,这也是 Boot 一贯的做法。