Skip to content

Commit b29f99f

Browse files
committed
Refactor ClientInterceptorFilter usage to isolate in factories
1 parent 58f2e52 commit b29f99f

File tree

9 files changed

+91
-42
lines changed

9 files changed

+91
-42
lines changed

spring-grpc-core/src/main/java/org/springframework/grpc/client/ClientInterceptorsConfigurer.java

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,7 @@
2121
import java.util.List;
2222

2323
import org.springframework.beans.factory.InitializingBean;
24-
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
25-
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
2624
import org.springframework.context.ApplicationContext;
27-
import org.springframework.core.log.LogAccessor;
2825
import org.springframework.grpc.internal.ApplicationContextBeanLookupUtils;
2926

3027
import io.grpc.ClientInterceptor;
@@ -38,14 +35,10 @@
3835
*/
3936
public class ClientInterceptorsConfigurer implements InitializingBean {
4037

41-
private final LogAccessor log = new LogAccessor(getClass());
42-
4338
private final ApplicationContext applicationContext;
4439

4540
private List<ClientInterceptor> globalInterceptors;
4641

47-
private ClientInterceptorFilter interceptorFilter;
48-
4942
public ClientInterceptorsConfigurer(ApplicationContext applicationContext) {
5043
this.applicationContext = applicationContext;
5144
}
@@ -65,9 +58,7 @@ protected void configureInterceptors(ManagedChannelBuilder<?> builder, List<Clie
6558
// Add specific interceptors
6659
allInterceptors.addAll(interceptors);
6760
// Filter all interceptors
68-
if (this.interceptorFilter != null) {
69-
allInterceptors.removeIf(interceptor -> !this.interceptorFilter.filter(interceptor, factory));
70-
}
61+
allInterceptors.removeIf(interceptor -> !factory.supports(interceptor));
7162
if (mergeWithGlobalInterceptors) {
7263
ApplicationContextBeanLookupUtils.sortBeansIncludingOrderAnnotation(this.applicationContext,
7364
ClientInterceptor.class, allInterceptors);
@@ -79,28 +70,11 @@ protected void configureInterceptors(ManagedChannelBuilder<?> builder, List<Clie
7970
@Override
8071
public void afterPropertiesSet() {
8172
this.globalInterceptors = findGlobalInterceptors();
82-
this.interceptorFilter = findInterceptorFilter();
8373
}
8474

8575
private List<ClientInterceptor> findGlobalInterceptors() {
8676
return ApplicationContextBeanLookupUtils.getBeansWithAnnotation(this.applicationContext,
8777
ClientInterceptor.class, GlobalClientInterceptor.class);
8878
}
8979

90-
private ClientInterceptorFilter findInterceptorFilter() {
91-
try {
92-
return this.applicationContext.getBean(ClientInterceptorFilter.class);
93-
}
94-
catch (NoUniqueBeanDefinitionException noUniqueBeanEx) {
95-
this.log.warn(noUniqueBeanEx,
96-
() -> "No unique ClientInterceptorFilter bean found. Consider defining a single bean or marking one as @Primary");
97-
return null;
98-
}
99-
catch (NoSuchBeanDefinitionException ignored) {
100-
this.log.debug(
101-
() -> "No ClientInterceptorFilter bean found - filtering will not be applied to client interceptors.");
102-
return null;
103-
}
104-
}
105-
10680
}

spring-grpc-core/src/main/java/org/springframework/grpc/client/CompositeGrpcChannelFactory.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.springframework.util.Assert;
2323

24+
import io.grpc.ClientInterceptor;
2425
import io.grpc.ManagedChannel;
2526

2627
/**
@@ -49,6 +50,11 @@ public boolean supports(String target) {
4950
return this.channelFactories.stream().anyMatch((cf) -> cf.supports(target));
5051
}
5152

53+
@Override
54+
public boolean supports(ClientInterceptor interceptor) {
55+
return this.channelFactories.stream().anyMatch((cf) -> cf.supports(interceptor));
56+
}
57+
5258
@Override
5359
public ManagedChannel createChannel(final String target, ChannelBuilderOptions options) {
5460
return this.channelFactories.stream()

spring-grpc-core/src/main/java/org/springframework/grpc/client/DefaultGrpcChannelFactory.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424

2525
import org.springframework.beans.factory.DisposableBean;
2626
import org.springframework.core.log.LogAccessor;
27+
import org.springframework.lang.Nullable;
2728
import org.springframework.util.Assert;
2829

2930
import io.grpc.ChannelCredentials;
31+
import io.grpc.ClientInterceptor;
3032
import io.grpc.Grpc;
3133
import io.grpc.ManagedChannel;
3234
import io.grpc.ManagedChannelBuilder;
@@ -52,10 +54,16 @@ public class DefaultGrpcChannelFactory<T extends ManagedChannelBuilder<T>>
5254

5355
private final ClientInterceptorsConfigurer interceptorsConfigurer;
5456

57+
private ClientInterceptorFilter interceptorFilter;
58+
5559
private ChannelCredentialsProvider credentials = ChannelCredentialsProvider.INSECURE;
5660

5761
private VirtualTargets targets = VirtualTargets.DEFAULT;
5862

63+
public void setInterceptorFilter(@Nullable ClientInterceptorFilter interceptorFilter) {
64+
this.interceptorFilter = interceptorFilter;
65+
}
66+
5967
/**
6068
* Construct a channel factory instance.
6169
* @param globalCustomizers the global customizers to apply to all created channels
@@ -82,6 +90,11 @@ public boolean supports(String target) {
8290
return !target.startsWith("in-process:");
8391
}
8492

93+
@Override
94+
public boolean supports(ClientInterceptor interceptor) {
95+
return this.interceptorFilter == null || this.interceptorFilter.filter(interceptor, this);
96+
}
97+
8598
public void setVirtualTargets(VirtualTargets targets) {
8699
this.targets = targets;
87100
}

spring-grpc-core/src/main/java/org/springframework/grpc/client/GrpcChannelFactory.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ public interface GrpcChannelFactory {
3838
*/
3939
boolean supports(String target);
4040

41+
/**
42+
* Whether this factory supports the given global {@link ClientInterceptor}.
43+
* @param interceptor the client interceptor to check support for
44+
* @return whether this factory supports the given interceptor
45+
*/
46+
default boolean supports(ClientInterceptor interceptor) {
47+
return true;
48+
}
49+
4150
/**
4251
* Creates a {@link ManagedChannel} for the given target string. The target can be
4352
* either a valid nameresolver-compliant URI, an authority string as described in

spring-grpc-core/src/test/java/org/springframework/grpc/client/ClientInterceptorsConfigurerTests.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ private void customizeContextAndRunConfigurer(
6868
List<ClientInterceptor> clientSpecificInterceptors, List<ClientInterceptor> expectedInterceptors) {
6969
ManagedChannelBuilder<?> builder = Mockito.mock();
7070
this.contextRunner().with(contextCustomizer).run((context) -> {
71-
var factory = Mockito.mock(GrpcChannelFactory.class);
7271
var configurer = context.getBean(ClientInterceptorsConfigurer.class);
72+
var factory = new DefaultGrpcChannelFactory<>(List.of(), configurer);
7373
configurer.configureInterceptors(builder, clientSpecificInterceptors, true, factory);
7474
// NOTE: the interceptors are called in reverse order per builder contract
7575
var expectedInterceptorsReversed = new ArrayList<>(expectedInterceptors);
@@ -131,13 +131,13 @@ void whenBlendInterceptorsFalseThenGlobalInterceptorsAddedFirst() {
131131
ClientInterceptorsConfigurerTests.this.contextRunner()
132132
.withUserConfiguration(GlobalClientInterceptorsConfig.class, ClientSpecificInterceptorsConfig.class)
133133
.run((context) -> {
134-
var factory = Mockito.mock(GrpcChannelFactory.class);
135134
var interceptorA = context.getBean("interceptorA", ClientInterceptor.class);
136135
var interceptorB = context.getBean("interceptorB", ClientInterceptor.class);
137136
var clientSpecificInterceptors = List.of(interceptorB, interceptorA);
138137
var expectedInterceptors = List.of(GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_BAR,
139138
GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_FOO, interceptorB, interceptorA);
140139
var configurer = context.getBean(ClientInterceptorsConfigurer.class);
140+
var factory = new DefaultGrpcChannelFactory<>(List.of(), configurer);
141141
configurer.configureInterceptors(builder, clientSpecificInterceptors, false, factory);
142142
// NOTE: the interceptors are called in reverse order per builder
143143
// contract
@@ -147,20 +147,19 @@ void whenBlendInterceptorsFalseThenGlobalInterceptorsAddedFirst() {
147147
});
148148
}
149149

150-
@SuppressWarnings("unchecked")
151150
@Test
152151
void whenBlendInterceptorsTrueThenGlobalInterceptorsBlended() {
153152
ManagedChannelBuilder<?> builder = Mockito.mock();
154153
ClientInterceptorsConfigurerTests.this.contextRunner()
155154
.withUserConfiguration(GlobalClientInterceptorsConfig.class, ClientSpecificInterceptorsConfig.class)
156155
.run((context) -> {
157-
var factory = Mockito.mock(GrpcChannelFactory.class);
158156
var interceptorA = context.getBean("interceptorA", ClientInterceptor.class);
159157
var interceptorB = context.getBean("interceptorB", ClientInterceptor.class);
160158
var clientSpecificInterceptors = List.of(interceptorB, interceptorA);
161159
var expectedInterceptors = List.of(GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_BAR,
162160
interceptorB, GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_FOO, interceptorA);
163161
var configurer = context.getBean(ClientInterceptorsConfigurer.class);
162+
var factory = new DefaultGrpcChannelFactory<>(List.of(), configurer);
164163
configurer.configureInterceptors(builder, clientSpecificInterceptors, true, factory);
165164
// NOTE: the interceptors are called in reverse order per builder
166165
// contract
@@ -178,13 +177,14 @@ class WithInterceptorFilter {
178177
@Test
179178
void whenFilterExcludesOneGlobalInterceptor_thenBuilderGetsOnlyAllowedOnes() {
180179
ManagedChannelBuilder<?> builder = Mockito.mock();
180+
ClientInterceptorFilter filter = (interceptor,
181+
__) -> interceptor == GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_BAR;
181182
ClientInterceptorsConfigurerTests.this.contextRunner()
182183
.withUserConfiguration(GlobalClientInterceptorsConfig.class)
183-
.withBean(ClientInterceptorFilter.class,
184-
() -> (interceptor, __) -> interceptor == GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_BAR)
185184
.run(context -> {
186-
var factory = Mockito.mock(GrpcChannelFactory.class);
187185
var configurer = context.getBean(ClientInterceptorsConfigurer.class);
186+
var factory = new InProcessGrpcChannelFactory(List.of(), configurer);
187+
factory.setInterceptorFilter(filter);
188188
configurer.configureInterceptors(builder, List.of(), true, factory);
189189
var expectedInterceptors = List.of(GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_BAR);
190190
verify(builder).intercept(expectedInterceptors);
@@ -194,12 +194,13 @@ void whenFilterExcludesOneGlobalInterceptor_thenBuilderGetsOnlyAllowedOnes() {
194194
@Test
195195
void whenFilterIncludesAllGlobalInterceptors_thenBuilderGetsOnlyAllowedOnes() {
196196
ManagedChannelBuilder<?> builder = Mockito.mock();
197+
ClientInterceptorFilter filter = (interceptor, __) -> true;
197198
ClientInterceptorsConfigurerTests.this.contextRunner()
198199
.withUserConfiguration(GlobalClientInterceptorsConfig.class)
199-
.withBean(ClientInterceptorFilter.class, () -> (__, ___) -> true)
200200
.run(context -> {
201-
var factory = Mockito.mock(GrpcChannelFactory.class);
202201
var configurer = context.getBean(ClientInterceptorsConfigurer.class);
202+
var factory = new InProcessGrpcChannelFactory(List.of(), configurer);
203+
factory.setInterceptorFilter(filter);
203204
configurer.configureInterceptors(builder, List.of(), true, factory);
204205
var expectedInterceptors = List.of(GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_BAR,
205206
GlobalClientInterceptorsConfig.GLOBAL_INTERCEPTOR_FOO);

spring-grpc-core/src/test/java/org/springframework/grpc/client/CompositeGrpcChannelFactoryTests.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
/**
3131
* Unit tests for the {@link CompositeGrpcChannelFactory}.
3232
*/
33-
@SuppressWarnings({ "unchecked", "rawtypes" })
3433
class CompositeGrpcChannelFactoryTests {
3534

3635
private TestChannelFactory fooChannelFactory;

spring-grpc-docs/src/main/antora/modules/ROOT/pages/client.adoc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ In the preceding example, the `globalLoggingInterceptor` is applied prior to the
247247
[[global-client-interceptor-filtering]]
248248
==== Filtering
249249
All global interceptors are applied to all created channels by default.
250-
However, you can register a `ClientInterceptorFilter` bean to decide which interceptors are applied to which channel factories.
250+
However, you can register a `ClientInterceptorFilter` to decide which interceptors are applied to which channel factories.
251251

252252
The following example prevents the `ExtraThingsInterceptor` interceptor from being applied to any channels created by the `InProcessGrpcChannelFactory` channel factory.
253253

@@ -260,6 +260,9 @@ ClientInterceptorFilter myInterceptorFilter() {
260260
}
261261
----
262262

263+
The `InProcessGrpcChannelFactory` picks up the `ClientInterceptorFilter` bean automatically and applies it to the global interceptors.
264+
For other channel factories, you can set the `interceptorFilter` property on the `GrpcChannelFactory` bean to the filter bean using a `GrpcChannelFactoryCustomizer`.
265+
263266
=== Per-Channel
264267
To add one or more client interceptors to be applied to a single client channel you can simply set the interceptor instance(s) on the options passed to the channel factory when creating the channel.
265268

spring-grpc-spring-boot-autoconfigure/src/main/java/org/springframework/grpc/autoconfigure/client/GrpcChannelFactoryConfigurations.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818

1919
import java.util.List;
2020

21+
import org.springframework.beans.factory.ObjectProvider;
2122
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2223
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2324
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2425
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2526
import org.springframework.context.annotation.Bean;
2627
import org.springframework.context.annotation.Configuration;
2728
import org.springframework.grpc.client.ChannelCredentialsProvider;
29+
import org.springframework.grpc.client.ClientInterceptorFilter;
2830
import org.springframework.grpc.client.ClientInterceptorsConfigurer;
2931
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
3032
import org.springframework.grpc.client.GrpcChannelFactory;
@@ -55,12 +57,15 @@ static class ShadedNettyChannelFactoryConfiguration {
5557
@Bean
5658
ShadedNettyGrpcChannelFactory shadedNettyGrpcChannelFactory(GrpcClientProperties properties,
5759
ChannelBuilderCustomizers channelBuilderCustomizers,
58-
ClientInterceptorsConfigurer interceptorsConfigurer, ChannelCredentialsProvider credentials) {
60+
ClientInterceptorsConfigurer interceptorsConfigurer,
61+
ObjectProvider<GrpcChannelFactoryCustomizer> channelFactoryCustomizers,
62+
ChannelCredentialsProvider credentials) {
5963
List<GrpcChannelBuilderCustomizer<io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder>> builderCustomizers = List
6064
.of(channelBuilderCustomizers::customize);
6165
var factory = new ShadedNettyGrpcChannelFactory(builderCustomizers, interceptorsConfigurer);
6266
factory.setCredentialsProvider(credentials);
6367
factory.setVirtualTargets(properties);
68+
channelFactoryCustomizers.orderedStream().forEach(customizer -> customizer.customize(factory));
6469
return factory;
6570
}
6671

@@ -77,12 +82,15 @@ static class NettyChannelFactoryConfiguration {
7782
@Bean
7883
NettyGrpcChannelFactory nettyGrpcChannelFactory(GrpcClientProperties properties,
7984
ChannelBuilderCustomizers channelBuilderCustomizers,
80-
ClientInterceptorsConfigurer interceptorsConfigurer, ChannelCredentialsProvider credentials) {
85+
ClientInterceptorsConfigurer interceptorsConfigurer,
86+
ObjectProvider<GrpcChannelFactoryCustomizer> channelFactoryCustomizers,
87+
ChannelCredentialsProvider credentials) {
8188
List<GrpcChannelBuilderCustomizer<NettyChannelBuilder>> builderCustomizers = List
8289
.of(channelBuilderCustomizers::customize);
8390
var factory = new NettyGrpcChannelFactory(builderCustomizers, interceptorsConfigurer);
8491
factory.setCredentialsProvider(credentials);
8592
factory.setVirtualTargets(properties);
93+
channelFactoryCustomizers.orderedStream().forEach(customizer -> customizer.customize(factory));
8694
return factory;
8795
}
8896

@@ -97,10 +105,18 @@ static class InProcessChannelFactoryConfiguration {
97105

98106
@Bean
99107
InProcessGrpcChannelFactory inProcessGrpcChannelFactory(ChannelBuilderCustomizers channelBuilderCustomizers,
100-
ClientInterceptorsConfigurer interceptorsConfigurer) {
108+
ClientInterceptorsConfigurer interceptorsConfigurer,
109+
ObjectProvider<ClientInterceptorFilter> interceptorFilter,
110+
ObjectProvider<GrpcChannelFactoryCustomizer> channelFactoryCustomizers) {
101111
List<GrpcChannelBuilderCustomizer<InProcessChannelBuilder>> inProcessBuilderCustomizers = List
102112
.of(channelBuilderCustomizers::customize);
103-
return new InProcessGrpcChannelFactory(inProcessBuilderCustomizers, interceptorsConfigurer);
113+
InProcessGrpcChannelFactory factory = new InProcessGrpcChannelFactory(inProcessBuilderCustomizers,
114+
interceptorsConfigurer);
115+
if (interceptorFilter != null) {
116+
factory.setInterceptorFilter(interceptorFilter.getIfAvailable(() -> null));
117+
}
118+
channelFactoryCustomizers.orderedStream().forEach(customizer -> customizer.customize(factory));
119+
return factory;
104120
}
105121

106122
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2024-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.grpc.autoconfigure.client;
17+
18+
import org.springframework.grpc.client.GrpcChannelFactory;
19+
20+
public interface GrpcChannelFactoryCustomizer {
21+
22+
/**
23+
* Customize the given {@link GrpcChannelFactory}.
24+
* @param factory the factory to customize
25+
*/
26+
void customize(GrpcChannelFactory factory);
27+
28+
}

0 commit comments

Comments
 (0)