跳到主要内容

gRPC 客户端

Doubao 中英对照 GRPC Clients GRPC Client

本节介绍 Spring gRPC 在客户端侧使用的核心概念。您项目中由 Protobuf 生成的源代码会包含存根类,它们只需绑定到一个通道即可。Protobuf 文件将由您所连接的服务提供。

通道工厂​

您可以创建一个 GrpcChannelFactory 并将其注入到应用配置中,用它来创建 gRPC 通道。默认的 GrpcChannelFactory 实现可以创建一个「具名」通道,随后您可用它提取连接到服务器所需的配置。例如:

@Bean
SimpleGrpc.SimpleBlockingStub stub(GrpcChannelFactory channels) {
return SimpleGrpc.newBlockingStub(channels.createChannel("local"));
}

「local」通道只是一个示例名称。您可以为通道使用任意名称。它由一个 VirtualTargets 实现解析,该实现会查找该通道的配置,并被注入到通道工厂中。

客户端自动配置​

为 gRPC 存根自动创建 Bean 的功能由 @ImportGrpcClients 注解开启。您可以添加该注解来更改存根的基类型,或扫描不同的包。Spring gRPC 会扫描应用包中的 gRPC 存根,并自动创建 Bean 定义。

包扫描​

@ImportGrpcClients 注解可用于控制对 gRPC 存根实现的扫描。要扫描某个包,您可以指定 basePackages 或 basePackageClasses 属性。随后在应用的其他位置,您就可以 @Autowired 注入生成的 gRPC 存根(默认是阻塞子类型)。您可以通过设置 factory 属性,将创建存根所用的工厂从 BlockingStubFactory 改为其他实现。常见存根类型已有预注册的标准工厂;如果您想注册额外的工厂,可以添加一个类型为 StubFactory 的 Bean(详见下文)。

在 Spring Boot 应用中,默认行为等同于在您的 @SpringBootApplication 类上添加如下配置:

// This is the default behaviour, so not necessary to add this annotation unless you change its attributes
@ImportGrpcClients(basePackageClasses = MyApplication.class)
@SpringBootApplication
class MyApplication {
// ...
}

您可以通过提供 spring.grpc.client.* 应用属性,或定义自己的 GrpcChannelBuilderCustomizer Bean,来增强并修改配置。

更复杂的示例​

GrpcChannelBuilderCustomizer 还可以控制通道的创建,并为存根(逐个地或通过扫描)添加自定义行为。例如,要仅为使用「stub」通道的客户端添加一个自定义安全拦截器:

@ImportGrpcClients(target = "stub", prefix = "secure", types = SimpleBlockingStub.class)
@Configuration
class ExtraConfiguration {

@Bean
GrpcChannelBuilderCustomizer<?> stubs() {
return GrpcChannelBuilderCustomizer.matches("stub", builder ->
builder.intercept(new BearerTokenAuthenticationInterceptor(() -> token(context))));
}

}

在本例中,我们没有扫描所有存根,而是将特定的存根类 SimpleBlockingStub 注册到名为 stub 的通道。前缀 secure 用作 Bean 定义名称的前缀,因此本例中最终的 Bean 定义名为「secureSimpleBlockingStub」。当您想让同一个存根类拥有多个不同配置的实例时,该特性很有用。

如果您有自定义的 StubFactory,请将其作为 @Bean 添加,并确保 Bean 定义具有正确的具体类型。然后在 @ImportGrpcClients 中将其指定为工厂类型。自定义工厂类型通常带有一个静态方法 supports(Class<?> type),它返回一个布尔值,指示该工厂能否创建给定类型的存根。如果它没有该静态方法,则该工厂将用于所列的所有显式存根类型(但不能用于扫描)。supports 方法必须是静态的,因为它在工厂创建之前就被调用,用于为存根生成 Bean 定义。

手动创建客户端​

我们可以不使用 @ImportGrpcClients 或 GrpcClientFactory 功能,而是手动创建一个客户端 @Bean。通道最常见的用法是创建一个绑定到某个服务的客户端。例如:

@Bean
SimpleGrpc.SimpleBlockingStub stub(GrpcChannelFactory channels) {
return SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:9090"));
}

通道配置​

通道工厂提供了用于创建通道的 API。通道的创建过程可按如下方式配置。

通道构建器定制器​

工厂用于创建通道的 ManagedChannelBuilder 可以在通道创建之前进行定制。

全局​

要定制用于所有已创建通道的构建器,您可以注册一个或多个 GrpcChannelBuilderCustomizer Bean。这些定制器会按照其 Bean 的自然顺序(即 @Order)依次应用到自动配置的 GrpcChannelFactory 上。

@Bean
@Order(100)
GrpcChannelBuilderCustomizer<NettyChannelBuilder> flowControlCustomizer() {
return (name, builder) -> builder.flowControlWindow(1024 * 1024);
}

@Bean
@Order(200)
<T extends ManagedChannelBuilder<T>> GrpcChannelBuilderCustomizer<T> retryChannelCustomizer() {
return (name, builder) -> builder.enableRetry().maxRetryAttempts(5);
}

在前述示例中,flowControlCustomizer 定制器先于 retryChannelCustomizer 应用。此外,flowControlCustomizer 仅在自动配置的通道工厂是 NettyGrpcChannelFactory 时才会应用。

按通道​

要定制某个单独的通道,您可以在创建通道时传给工厂的选项上指定一个 GrpcChannelBuilderCustomizer。按通道定制器将在所有全局定制器之后应用。

@Bean
SimpleGrpc.SimpleBlockingStub stub(GrpcChannelFactory channelFactory) {
ChannelBuilderOptions options = ChannelBuilderOptions.defaults()
.withCustomizer((__, b) -> b.disableRetry());
ManagedChannel channel = channelFactory.createChannel("localhost", options);
return SimpleGrpc.newBlockingStub(channel);
}

上面的示例仅为这一个创建的通道禁用了重试。

注意

尽管通道构建器定制器让您可以完全访问原生通道构建器,但您不应在定制后的构建器上调用 build,因为通道工厂会替您处理 build 调用;自行调用会产生孤立的通道。

客户端拦截器​

全局​

要添加一个应用于所有已创建通道的客户端拦截器,您只需注册一个客户端拦截器 Bean,然后用 @GlobalClientInterceptor 注解标注它。当您注册多个拦截器 Bean 时,它们会按照 Bean 的自然顺序(即 @Order)排序。

@Bean
@Order(100)
@GlobalClientInterceptor
ClientInterceptor globalLoggingInterceptor() {
return new LoggingInterceptor();
}

@Bean
@Order(200)
@GlobalClientInterceptor
ClientInterceptor globalExtraThingsInterceptor() {
return new ExtraThingsInterceptor();
}

在前述示例中,globalLoggingInterceptor 先于 globalExtraThingsInterceptor 应用。

过滤​

默认情况下,所有全局拦截器都会应用到所有已创建的通道。不过,您可以注册一个 ClientInterceptorFilter,用来决定哪些拦截器应用于哪些通道工厂。

以下示例阻止 ExtraThingsInterceptor 拦截器应用于由 InProcessGrpcChannelFactory 通道工厂创建的任何通道。

@Bean
ClientInterceptorFilter myInterceptorFilter() {
return (interceptor, factory) -> !(interceptor instanceof ExtraThingsInterceptor
&& factory instanceof InProcessGrpcChannelFactory);
}

InProcessGrpcChannelFactory 会自动拾取 ClientInterceptorFilter Bean,并将其应用到全局拦截器上。对于其他通道工厂,请注册一个类型为 java.util.function.Consumer<org.springframework.grpc.client.GrpcChannelFactory> 的 Bean,在其中将其强制转换为 DefaultGrpcChannelFactory(或另一个具体实现),并用您的过滤器调用 setInterceptorFilter。

按通道​

要为单个客户端通道添加一个或多个客户端拦截器,您只需在创建通道时,在传给通道工厂的选项上设置拦截器实例。

@Bean
SimpleGrpc.SimpleBlockingStub stub(GrpcChannelFactory channelFactory) {
ClientInterceptor interceptor1 = getChannelInterceptor1();
ClientInterceptor interceptor2 = getChannelInterceptor2();
ChannelBuilderOptions options = ChannelBuilderOptions.defaults()
.withInterceptors(List.of(interceptor1, interceptor2));
ManagedChannel channel = channelFactory.createChannel("localhost", options);
return SimpleGrpc.newBlockingStub(channel);
}

上面的示例先对这个创建的通道应用 interceptor1,再应用 interceptor2。

注意

尽管通道构建器定制器让您可以完全访问原生通道构建器,但我们建议不要在定制后的构建器上调用 intercept,而是如上所述使用 ChannelBuilderOptions 设置按通道拦截器。如果您确实直接在构建器上调用了 intercept,那么这些拦截器将先于上文所述的「全局」和「按通道」拦截器应用。

混合​

当一个通道同时配置了全局拦截器和按通道拦截器时,先按排序后的顺序应用全局拦截器,再按排序后的顺序应用按通道拦截器。

不过,通过将传给通道工厂的 ChannelBuilderOptions 上的 withInterceptorsMerge 参数设置为 "true",您可以改变这一行为:所有拦截器会先合并在一起,然后按照 Bean 的自然顺序(即 @Order 或 Ordered 接口)排序。

如果您想在全局拦截器之间插入一个按客户端拦截器,可以使用该选项。

important

您传入的按通道拦截器必须是用 @Order 标注的 Bean 实例,或是实现了 Ordered 接口的普通对象,才能与全局拦截器正确地合并/排序。

可观测性​

Spring gRPC 提供了一个自动配置的拦截器,可用于为您的 gRPC 客户端提供可观测性。

安全​

如果您的远程 gRPC 服务器要求请求经过认证,您就需要配置客户端以提供认证凭据。

HTTP 头​

Spring gRPC 提供了几个拦截器,可用于为您的 gRPC 客户端提供安全保障。其中一个用于 Basic HTTP 认证,另一个用于 OAuth2(承载令牌)。下面是一个创建使用 Basic HTTP 认证的通道的示例:

@Bean
@Lazy
Channel basic(GrpcChannelFactory channels) {
return channels.createChannel("my-channel", ChannelBuilderOptions.defaults()
.withInterceptors(List.of(new BasicAuthenticationInterceptor("user", "password"))));
}

承载令牌拦截器的用法与此类似。您可以查看 BasicAuthenticationInterceptor 和 BearerTokenAuthenticationInterceptor 的实现,了解如何为自定义请求头创建自己的拦截器。

OAuth2 客户端​

Spring gRPC 提供了一个自动配置的 OAuth2 客户端,可用于为您的 gRPC 客户端提供认证。它的工作方式与任何 Spring Boot 应用中相同:如果您在 spring.security.oauth2.authorizationserver.client.* 中配置属性,就能够注入 ClientRegistrationRepository,并用它为给定的客户端注册创建一个 OAuth2AuthorizedClient。下面的示例展示了如何将客户端注册接入 gRPC 客户端中的 BearerTokenAuthenticationInterceptor:

@Bean
@Lazy
SimpleGrpc.SimpleBlockingStub basic(GrpcChannelFactory channels, ClientRegistrationRepository registry) {
ClientRegistration reg = registry.findByRegistrationId("spring");
return SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:9090", ChannelBuilderOptions.defaults()
.withInterceptors(List.of(new BearerTokenAuthenticationInterceptor(() -> token(reg))))));
}

private String token(ClientRegistration reg) {
RestClientClientCredentialsTokenResponseClient creds = new RestClientClientCredentialsTokenResponseClient();
String token = creds.getTokenResponse(new OAuth2ClientCredentialsGrantRequest(reg))
.getAccessToken()
.getTokenValue();
return token;
}