Add client interceptors to channel factory

This commit adds support for global and channel-specific client
interceptors.

Resolves #52

Signed-off-by: Chris Bono <chris.bono@gmail.com>
This commit is contained in:
Chris Bono
2024-12-01 12:48:18 -06:00
committed by Dave Syer
parent bfa54f9423
commit 5c0ca7056c
11 changed files with 532 additions and 22 deletions

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* */
package org.springframework.grpc.client;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.grpc.internal.ApplicationContextBeanLookupUtils;
import io.grpc.ClientInterceptor;
import io.grpc.ManagedChannelBuilder;
/**
* Configure a {@link ManagedChannelBuilder} with client interceptors.
*
* @author Chris Bono
*/
public class ClientInterceptorsConfigurer implements InitializingBean {
private final ApplicationContext applicationContext;
private List<ClientInterceptor> globalInterceptors;
public ClientInterceptorsConfigurer(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* Configure a {@link ManagedChannelBuilder} with client interceptors.
* @param builder the builder to configure
* @param interceptors the non-null list of interceptors to be applied to the channel
* @param mergeWithGlobalInterceptors whether the provided interceptors should be
* blended with the global interceptors.
*/
protected void configureInterceptors(ManagedChannelBuilder<?> builder, List<ClientInterceptor> interceptors,
boolean mergeWithGlobalInterceptors) {
// Add global interceptors first
List<ClientInterceptor> allInterceptors = new ArrayList<>(this.globalInterceptors);
// Add specific interceptors
allInterceptors.addAll(interceptors);
if (mergeWithGlobalInterceptors) {
ApplicationContextBeanLookupUtils.sortBeansIncludingOrderAnnotation(this.applicationContext,
ClientInterceptor.class, allInterceptors);
}
Collections.reverse(allInterceptors);
builder.intercept(allInterceptors);
}
@Override
public void afterPropertiesSet() {
this.globalInterceptors = findGlobalInterceptors();
}
private List<ClientInterceptor> findGlobalInterceptors() {
return ApplicationContextBeanLookupUtils.getBeansWithAnnotation(this.applicationContext,
ClientInterceptor.class, GlobalClientInterceptor.class);
}
}

View File

@@ -25,6 +25,7 @@ import org.springframework.beans.factory.DisposableBean;
import org.springframework.util.Assert;
import io.grpc.ChannelCredentials;
import io.grpc.ClientInterceptor;
import io.grpc.ForwardingChannelBuilder2;
import io.grpc.Grpc;
import io.grpc.ManagedChannel;
@@ -47,17 +48,18 @@ public class DefaultGrpcChannelFactory implements GrpcChannelFactory, Disposable
private final List<GrpcChannelBuilderCustomizer> customizers = new ArrayList<>();
private final ClientInterceptorsConfigurer interceptorsConfigurer;
private ChannelCredentialsProvider credentials = ChannelCredentialsProvider.INSECURE;
private VirtualTargets targets = VirtualTargets.DEFAULT;
public DefaultGrpcChannelFactory() {
this(List.of());
}
public DefaultGrpcChannelFactory(List<GrpcChannelBuilderCustomizer> customizers) {
public DefaultGrpcChannelFactory(List<GrpcChannelBuilderCustomizer> customizers,
ClientInterceptorsConfigurer interceptorsConfigurer) {
Assert.notNull(customizers, () -> "customizers must not be null");
Assert.notNull(interceptorsConfigurer, () -> "interceptorsConfigurer must not be null");
this.customizers.addAll(customizers);
this.interceptorsConfigurer = interceptorsConfigurer;
}
public void setVirtualTargets(VirtualTargets targets) {
@@ -70,13 +72,20 @@ public class DefaultGrpcChannelFactory implements GrpcChannelFactory, Disposable
@Override
public ManagedChannelBuilder<?> createChannel(String authority) {
ManagedChannelBuilder<?> target = this.builders.computeIfAbsent(authority, path -> {
ManagedChannelBuilder<?> builder = newChannel(this.targets.getTarget(path),
return this.createChannel(authority, List.of(), false);
}
@Override
public ManagedChannelBuilder<?> createChannel(String authority, List<ClientInterceptor> interceptors,
boolean mergeWithGlobalInterceptors) {
Assert.notNull(interceptors, () -> "interceptors must not be null");
return this.builders.computeIfAbsent(authority, path -> {
ManagedChannelBuilder<?> builder = newChannelBuilder(this.targets.getTarget(path),
this.credentials.getChannelCredentials(path));
this.interceptorsConfigurer.configureInterceptors(builder, interceptors, mergeWithGlobalInterceptors);
this.customizers.forEach((c) -> c.customize(path, builder));
return builder;
return new DisposableChannelBuilder(authority, builder);
});
return new DisposableChannelBuilder(authority, target);
}
/**
@@ -86,15 +95,17 @@ public class DefaultGrpcChannelFactory implements GrpcChannelFactory, Disposable
* @param creds the credentials for the channel
* @return a new {@link ManagedChannelBuilder} for the given path and credentials
*/
protected ManagedChannelBuilder<?> newChannel(String path, ChannelCredentials creds) {
protected ManagedChannelBuilder<?> newChannelBuilder(String path, ChannelCredentials creds) {
return Grpc.newChannelBuilder(path, creds);
}
private ManagedChannel buildAndRegisterChannel(String channelName, ManagedChannelBuilder<?> channelBuilder) {
return this.channels.computeIfAbsent(channelName, (__) -> channelBuilder.build());
}
@Override
public void destroy() {
for (ManagedChannel channel : this.channels.values()) {
channel.shutdown();
}
this.channels.values().forEach(ManagedChannel::shutdown);
}
/**
@@ -103,10 +114,10 @@ public class DefaultGrpcChannelFactory implements GrpcChannelFactory, Disposable
*/
class DisposableChannelBuilder extends ForwardingChannelBuilder2<DisposableChannelBuilder> {
private final ManagedChannelBuilder<?> delegate;
private final String authority;
private final ManagedChannelBuilder<?> delegate;
DisposableChannelBuilder(String authority, ManagedChannelBuilder<?> delegate) {
this.authority = authority;
this.delegate = delegate;
@@ -119,7 +130,7 @@ public class DefaultGrpcChannelFactory implements GrpcChannelFactory, Disposable
@Override
public ManagedChannel build() {
return DefaultGrpcChannelFactory.this.channels.computeIfAbsent(this.authority, name -> super.build());
return DefaultGrpcChannelFactory.this.buildAndRegisterChannel(this.authority, this.delegate);
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2024-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Partial copy from net.devh:grpc-spring-boot-starter.
*/
package org.springframework.grpc.client;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.Order;
import io.grpc.ClientInterceptor;
/**
* Annotation that can be specified on a gRPC {@link ClientInterceptor} bean which will
* result in the interceptor being applied globally to channels.
* <p>
* The bean interceptor {@link Order} will be respected.
*
* @author Daniel Theuke (daniel.theuke@heuboe.de)
* @author Chris Bono
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GlobalClientInterceptor {
}

View File

@@ -16,6 +16,9 @@
package org.springframework.grpc.client;
import java.util.List;
import io.grpc.ClientInterceptor;
import io.grpc.ManagedChannelBuilder;
/**
@@ -34,4 +37,16 @@ public interface GrpcChannelFactory {
*/
ManagedChannelBuilder<?> createChannel(String authority);
/**
* Creates a {@link ManagedChannelBuilder} for the given authority and the provided
* client interceptors.
* @param authority the target authority for the channel
* @param interceptors the non-null list of interceptors to be applied to the channel
* @param mergeWithGlobalInterceptors whether the provided interceptors should be
* blended with the global interceptors.
* @return a channel builder conifgured with the provided values
*/
ManagedChannelBuilder<?> createChannel(String authority, List<ClientInterceptor> interceptors,
boolean mergeWithGlobalInterceptors);
}

View File

@@ -0,0 +1,241 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.grpc.client;
import static org.mockito.Mockito.verify;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import io.grpc.ClientInterceptor;
import io.grpc.ManagedChannelBuilder;
/**
* Tests for {@link ClientInterceptorsConfigurer}.
*
* @author Chris Bono
*/
class ClientInterceptorsConfigurerTests {
private ApplicationContextRunner contextRunner() {
return new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ClientInterceptorsConfigurerConfig.class));
}
@Test
void globalClientInterceptorsAreFoundInProperOrder() {
this.contextRunner()
.withUserConfiguration(GlobalClientInterceptorsConfig.class)
.run((context) -> Assertions.assertThat(context)
.getBean(ClientInterceptorsConfigurer.class)
.extracting("globalInterceptors", InstanceOfAssertFactories.LIST)
.containsExactly(GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_BAR,
GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_FOO));
}
private void customizeContextAndRunConfigurer(
Function<ApplicationContextRunner, ApplicationContextRunner> contextCustomizer,
List<ClientInterceptor> clientSpecificInterceptors, List<ClientInterceptor> expectedInterceptors) {
ManagedChannelBuilder<?> builder = Mockito.mock();
this.contextRunner().with(contextCustomizer).run((context) -> {
var configurer = context.getBean(ClientInterceptorsConfigurer.class);
configurer.configureInterceptors(builder, clientSpecificInterceptors, true);
// NOTE: the interceptors are called in reverse order per builder contract
var expectedInterceptorsReversed = new ArrayList<>(expectedInterceptors);
Collections.reverse(expectedInterceptorsReversed);
verify(builder).intercept(expectedInterceptorsReversed);
});
}
@Nested
class WithOnlyGlobalInterceptors {
@Test
void whenNoGlobalInterceptorsRegisteredThenBuilderGetsNoInterceptors() {
customizeContextAndRunConfigurer(Function.identity(), List.of(), List.of());
}
@Test
void whenGlobalInterceptorsRegisteredThenBuilderGetsGlobalInterceptors() {
customizeContextAndRunConfigurer(
(contextRunner) -> contextRunner.withUserConfiguration(GlobalClientInterceptorsConfig.class),
List.of(), List.of(GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_BAR,
GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_FOO));
}
}
@Nested
class WithOnlyClientSpecificInterceptors {
@Test
void whenSingleInterceptorSpecifiedThenItIsUsed() {
var clientSpecificInterceptors = List
.<ClientInterceptor>of(ClientSpecificInterceptorsConfig.SVC_INTERCEPTOR_A);
var expectedInterceptors = List.<ClientInterceptor>of(ClientSpecificInterceptorsConfig.SVC_INTERCEPTOR_A);
customizeContextAndRunConfigurer(
(contextRunner) -> contextRunner.withUserConfiguration(ClientSpecificInterceptorsConfig.class),
clientSpecificInterceptors, expectedInterceptors);
}
@Test
void whenMultipleInterceptorsSpecifiedThenTheyAreUsed() {
var clientSpecificInterceptors = List.of(ClientSpecificInterceptorsConfig.SVC_INTERCEPTOR_B,
ClientSpecificInterceptorsConfig.SVC_INTERCEPTOR_A);
var expectedInterceptors = List.of(ClientSpecificInterceptorsConfig.SVC_INTERCEPTOR_B,
ClientSpecificInterceptorsConfig.SVC_INTERCEPTOR_A);
customizeContextAndRunConfigurer(
(contextRunner) -> contextRunner.withUserConfiguration(ClientSpecificInterceptorsConfig.class),
clientSpecificInterceptors, expectedInterceptors);
}
}
@Nested
class WithClientSpecificCombinedWithGlobalInterceptors {
@Test
void whenBlendInterceptorsFalseThenGlobalInterceptorsAddedFirst() {
ManagedChannelBuilder<?> builder = Mockito.mock();
ClientInterceptorsConfigurerTests.this.contextRunner()
.withUserConfiguration(GlobalClientInterceptorsConfig.class, ClientSpecificInterceptorsConfig.class)
.run((context) -> {
var interceptorA = context.getBean("interceptorA", ClientInterceptor.class);
var interceptorB = context.getBean("interceptorB", ClientInterceptor.class);
var clientSpecificInterceptors = List.of(interceptorB, interceptorA);
var expectedInterceptors = List.of(GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_BAR,
GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_FOO, interceptorB, interceptorA);
var configurer = context.getBean(ClientInterceptorsConfigurer.class);
configurer.configureInterceptors(builder, clientSpecificInterceptors, false);
// NOTE: the interceptors are called in reverse order per builder
// contract
var expectedInterceptorsReversed = new ArrayList<>(expectedInterceptors);
Collections.reverse(expectedInterceptorsReversed);
verify(builder).intercept(expectedInterceptorsReversed);
});
}
@SuppressWarnings("unchecked")
@Test
void whenBlendInterceptorsTrueThenGlobalInterceptorsBlended() {
ManagedChannelBuilder<?> builder = Mockito.mock();
ClientInterceptorsConfigurerTests.this.contextRunner()
.withUserConfiguration(GlobalClientInterceptorsConfig.class, ClientSpecificInterceptorsConfig.class)
.run((context) -> {
var interceptorA = context.getBean("interceptorA", ClientInterceptor.class);
var interceptorB = context.getBean("interceptorB", ClientInterceptor.class);
var clientSpecificInterceptors = List.of(interceptorB, interceptorA);
var expectedInterceptors = List.of(GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_BAR,
interceptorB, GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_FOO, interceptorA);
var configurer = context.getBean(ClientInterceptorsConfigurer.class);
configurer.configureInterceptors(builder, clientSpecificInterceptors, true);
// NOTE: the interceptors are called in reverse order per builder
// contract
var expectedInterceptorsReversed = new ArrayList<>(expectedInterceptors);
Collections.reverse(expectedInterceptorsReversed);
verify(builder).intercept(expectedInterceptorsReversed);
});
}
}
interface TestClientInterceptorA extends ClientInterceptor {
}
interface TestClientInterceptorB extends ClientInterceptor {
}
@Configuration(proxyBeanMethods = false)
static class ClientInterceptorsConfigurerConfig {
@Bean
ClientInterceptorsConfigurer clientInterceptorsConfigurer(ApplicationContext applicationContext) {
return new ClientInterceptorsConfigurer(applicationContext);
}
}
@Configuration(proxyBeanMethods = false)
static class GlobalClientInterceptorsConfig {
static ClientInterceptor GLOBAL_INTERCEPTOR_FOO = Mockito.mock();
static ClientInterceptor GLOBAL_INTERCEPTOR_IGNORED = Mockito.mock();
static ClientInterceptor GLOBAL_INTERCEPTOR_BAR = Mockito.mock();
@Bean
@Order(200)
@GlobalClientInterceptor
ClientInterceptor globalInterceptorFoo() {
return GLOBAL_INTERCEPTOR_FOO;
}
@Bean
@Order(150)
ClientInterceptor globalInterceptorIgnored() {
return GLOBAL_INTERCEPTOR_IGNORED;
}
@Bean
@Order(100)
@GlobalClientInterceptor
ClientInterceptor globalInterceptorBar() {
return GLOBAL_INTERCEPTOR_BAR;
}
}
@Configuration(proxyBeanMethods = false)
static class ClientSpecificInterceptorsConfig {
static TestClientInterceptorB SVC_INTERCEPTOR_B = Mockito.mock();
static TestClientInterceptorA SVC_INTERCEPTOR_A = Mockito.mock();
@Bean
@Order(150)
TestClientInterceptorB interceptorB() {
return SVC_INTERCEPTOR_B;
}
@Bean
@Order(225)
TestClientInterceptorA interceptorA() {
return SVC_INTERCEPTOR_A;
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.grpc.client;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.util.List;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.springframework.grpc.client.DefaultGrpcChannelFactory.DisposableChannelBuilder;
import io.grpc.ClientInterceptor;
/**
* Tests for {@link DefaultGrpcChannelFactory}.
*/
class DefaultGrpcChannelFactoryTests {
@Test
void createChannelWithoutClientSpecificInterceptorsInvokesInterceptorConfigurer() {
ClientInterceptorsConfigurer configurer = mock();
var channelName = "testChannel";
var channelFactory = new DefaultGrpcChannelFactory(List.of(), configurer);
channelFactory.createChannel(channelName);
// Get the actual builder that should be passed into the configurer
assertThat(channelFactory)
.extracting("builders", InstanceOfAssertFactories.map(String.class, DisposableChannelBuilder.class))
.hasEntrySatisfying(channelName,
(builder) -> verify(configurer).configureInterceptors(builder.delegate(), List.of(), false));
}
@Test
void createChannelWithClientSpecificInterceptorsInvokesInterceptorConfigurer() {
ClientInterceptorsConfigurer configurer = mock();
var channelName = "testChannel";
var channelFactory = new DefaultGrpcChannelFactory(List.of(), configurer);
var interceptor = mock(ClientInterceptor.class);
var interceptors = List.of(interceptor);
channelFactory.createChannel(channelName, interceptors, true);
// Get the actual builder that should be passed into the configurer
assertThat(channelFactory)
.extracting("builders", InstanceOfAssertFactories.map(String.class, DisposableChannelBuilder.class))
.hasEntrySatisfying(channelName,
(builder) -> verify(configurer).configureInterceptors(builder.delegate(), interceptors, true));
}
}

View File

@@ -100,3 +100,34 @@ SimpleGrpc.SimpleBlockingStub stub(GrpcChannelFactory channels, @LocalGrpcPort i
return SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:" + port).build());
}
----
[[client-interceptor]]
== Client Interceptors
=== Global
To add a client interceptor to be applied to all created channels you can simply register a client interceptor bean and then annotate it with `@GlobalClientInterceptor`.
The interceptors are ordered according to their bean natural ordering (i.e. `@Order`).
[source,java]
----
@Bean
@Order(100)
@GlobalClientInterceptor
ClientInterceptor myGlobalLoggingInterceptor() {
return new MyLoggingInterceptor();
}
----
=== Per-Channel
To add one or more client interceptors to be applied to a single client channel you can simply pass in the interceptor instance(s) when invoking the channel factory to create the channel.
The interceptors are ordered according to their position in the specified list.
=== Blended
When a channel is constructed with both global and per-client interceptors, the global interceptors are first applied in their sorted order followed by the per-service interceptors in their sorted order.
However, by setting the `mergeWithGlobalInterceptors` parameter on the channel factory to `"true"` you can change this behavior so that the interceptors are all combined and then sorted according to their bean natural ordering (i.e. `@Order` or `Ordered` interface).
You can use this option if you want to add a per-client interceptor between global interceptors.
IMPORTANT: The per-channel interceptors you pass in must either be bean instances marked with `@Order` or regular objects that implement the `Ordered` interface to be properly merged/ordered with the global interceptors.

View File

@@ -6,6 +6,8 @@
This section covers the changes made from version 0.2.0 to version 0.3.0.
=== Client Interceptors
You can now add xref:client.adoc#client-interceptor[Client Interceptors] to created gRPC channels.
=== Breaking Changes

View File

@@ -21,12 +21,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.grpc.autoconfigure.client.GrpcClientProperties.NamedChannel;
import org.springframework.grpc.autoconfigure.common.codec.GrpcCodecConfiguration;
import org.springframework.grpc.client.ChannelCredentialsProvider;
import org.springframework.grpc.client.ClientInterceptorsConfigurer;
import org.springframework.grpc.client.DefaultGrpcChannelFactory;
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
import org.springframework.grpc.client.GrpcChannelFactory;
@@ -40,11 +42,18 @@ import io.grpc.DecompressorRegistry;
@Import(GrpcCodecConfiguration.class)
public class GrpcClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
ClientInterceptorsConfigurer clientInterceptorsConfigurer(ApplicationContext applicationContext) {
return new ClientInterceptorsConfigurer(applicationContext);
}
@Bean
@ConditionalOnMissingBean(GrpcChannelFactory.class)
public DefaultGrpcChannelFactory defaultGrpcChannelFactory(List<GrpcChannelBuilderCustomizer> customizers,
ChannelCredentialsProvider credentials, GrpcClientProperties channels, SslBundles ignored) {
DefaultGrpcChannelFactory factory = new DefaultGrpcChannelFactory(customizers);
ClientInterceptorsConfigurer interceptorsConfigurer, ChannelCredentialsProvider credentials,
GrpcClientProperties channels, SslBundles ignored) {
DefaultGrpcChannelFactory factory = new DefaultGrpcChannelFactory(customizers, interceptorsConfigurer);
factory.setCredentialsProvider(credentials);
factory.setVirtualTargets(new NamedChannelVirtualTargets(channels));
return factory;

View File

@@ -15,6 +15,9 @@
*/
package org.springframework.grpc.test;
import java.util.List;
import org.springframework.grpc.client.ClientInterceptorsConfigurer;
import org.springframework.grpc.client.DefaultGrpcChannelFactory;
import io.grpc.ChannelCredentials;
@@ -23,8 +26,12 @@ import io.grpc.inprocess.InProcessChannelBuilder;
public class InProcessGrpcChannelFactory extends DefaultGrpcChannelFactory {
public InProcessGrpcChannelFactory(ClientInterceptorsConfigurer interceptorsConfigurer) {
super(List.of(), interceptorsConfigurer);
}
@Override
protected ManagedChannelBuilder<?> newChannel(String path, ChannelCredentials creds) {
protected ManagedChannelBuilder<?> newChannelBuilder(String path, ChannelCredentials creds) {
return InProcessChannelBuilder.forName(path);
}

View File

@@ -20,11 +20,14 @@ import java.util.List;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.grpc.autoconfigure.client.GrpcClientAutoConfiguration;
import org.springframework.grpc.autoconfigure.server.GrpcServerFactoryAutoConfiguration;
import org.springframework.grpc.client.ClientInterceptorsConfigurer;
import org.springframework.grpc.server.ServerBuilderCustomizer;
import org.springframework.grpc.server.service.GrpcServiceDiscoverer;
@@ -37,7 +40,7 @@ import io.grpc.inprocess.InProcessServerBuilder;
@ConditionalOnNotWebApplication
public class InProcessGrpcServerFactoryAutoConfiguration {
private String address = InProcessServerBuilder.generateName();
private final String address = InProcessServerBuilder.generateName();
@Bean
@ConditionalOnBean(BindableService.class)
@@ -49,8 +52,14 @@ public class InProcessGrpcServerFactoryAutoConfiguration {
}
@Bean
InProcessGrpcChannelFactory grpcChannelFactory() {
InProcessGrpcChannelFactory factory = new InProcessGrpcChannelFactory();
@ConditionalOnMissingBean
ClientInterceptorsConfigurer inProcessClientInterceptorsConfigurer(ApplicationContext applicationContext) {
return new ClientInterceptorsConfigurer(applicationContext);
}
@Bean
InProcessGrpcChannelFactory grpcChannelFactory(ClientInterceptorsConfigurer interceptorsConfigurer) {
InProcessGrpcChannelFactory factory = new InProcessGrpcChannelFactory(interceptorsConfigurer);
factory.setVirtualTargets(path -> address);
return factory;
}