Skip to content

Commit 5c0ca70

Browse files
onobcdsyer
authored andcommitted
Add client interceptors to channel factory
This commit adds support for global and channel-specific client interceptors. Resolves #52 Signed-off-by: Chris Bono <[email protected]>
1 parent bfa54f9 commit 5c0ca70

File tree

11 files changed

+532
-22
lines changed

11 files changed

+532
-22
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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+
17+
package org.springframework.grpc.client;
18+
19+
import java.util.ArrayList;
20+
import java.util.Collections;
21+
import java.util.List;
22+
23+
import org.springframework.beans.factory.InitializingBean;
24+
import org.springframework.context.ApplicationContext;
25+
import org.springframework.grpc.internal.ApplicationContextBeanLookupUtils;
26+
27+
import io.grpc.ClientInterceptor;
28+
import io.grpc.ManagedChannelBuilder;
29+
30+
/**
31+
* Configure a {@link ManagedChannelBuilder} with client interceptors.
32+
*
33+
* @author Chris Bono
34+
*/
35+
public class ClientInterceptorsConfigurer implements InitializingBean {
36+
37+
private final ApplicationContext applicationContext;
38+
39+
private List<ClientInterceptor> globalInterceptors;
40+
41+
public ClientInterceptorsConfigurer(ApplicationContext applicationContext) {
42+
this.applicationContext = applicationContext;
43+
}
44+
45+
/**
46+
* Configure a {@link ManagedChannelBuilder} with client interceptors.
47+
* @param builder the builder to configure
48+
* @param interceptors the non-null list of interceptors to be applied to the channel
49+
* @param mergeWithGlobalInterceptors whether the provided interceptors should be
50+
* blended with the global interceptors.
51+
*/
52+
protected void configureInterceptors(ManagedChannelBuilder<?> builder, List<ClientInterceptor> interceptors,
53+
boolean mergeWithGlobalInterceptors) {
54+
// Add global interceptors first
55+
List<ClientInterceptor> allInterceptors = new ArrayList<>(this.globalInterceptors);
56+
// Add specific interceptors
57+
allInterceptors.addAll(interceptors);
58+
if (mergeWithGlobalInterceptors) {
59+
ApplicationContextBeanLookupUtils.sortBeansIncludingOrderAnnotation(this.applicationContext,
60+
ClientInterceptor.class, allInterceptors);
61+
}
62+
Collections.reverse(allInterceptors);
63+
builder.intercept(allInterceptors);
64+
}
65+
66+
@Override
67+
public void afterPropertiesSet() {
68+
this.globalInterceptors = findGlobalInterceptors();
69+
}
70+
71+
private List<ClientInterceptor> findGlobalInterceptors() {
72+
return ApplicationContextBeanLookupUtils.getBeansWithAnnotation(this.applicationContext,
73+
ClientInterceptor.class, GlobalClientInterceptor.class);
74+
}
75+
76+
}

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

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.util.Assert;
2626

2727
import io.grpc.ChannelCredentials;
28+
import io.grpc.ClientInterceptor;
2829
import io.grpc.ForwardingChannelBuilder2;
2930
import io.grpc.Grpc;
3031
import io.grpc.ManagedChannel;
@@ -47,17 +48,18 @@ public class DefaultGrpcChannelFactory implements GrpcChannelFactory, Disposable
4748

4849
private final List<GrpcChannelBuilderCustomizer> customizers = new ArrayList<>();
4950

51+
private final ClientInterceptorsConfigurer interceptorsConfigurer;
52+
5053
private ChannelCredentialsProvider credentials = ChannelCredentialsProvider.INSECURE;
5154

5255
private VirtualTargets targets = VirtualTargets.DEFAULT;
5356

54-
public DefaultGrpcChannelFactory() {
55-
this(List.of());
56-
}
57-
58-
public DefaultGrpcChannelFactory(List<GrpcChannelBuilderCustomizer> customizers) {
57+
public DefaultGrpcChannelFactory(List<GrpcChannelBuilderCustomizer> customizers,
58+
ClientInterceptorsConfigurer interceptorsConfigurer) {
5959
Assert.notNull(customizers, () -> "customizers must not be null");
60+
Assert.notNull(interceptorsConfigurer, () -> "interceptorsConfigurer must not be null");
6061
this.customizers.addAll(customizers);
62+
this.interceptorsConfigurer = interceptorsConfigurer;
6163
}
6264

6365
public void setVirtualTargets(VirtualTargets targets) {
@@ -70,13 +72,20 @@ public void setCredentialsProvider(ChannelCredentialsProvider credentials) {
7072

7173
@Override
7274
public ManagedChannelBuilder<?> createChannel(String authority) {
73-
ManagedChannelBuilder<?> target = this.builders.computeIfAbsent(authority, path -> {
74-
ManagedChannelBuilder<?> builder = newChannel(this.targets.getTarget(path),
75+
return this.createChannel(authority, List.of(), false);
76+
}
77+
78+
@Override
79+
public ManagedChannelBuilder<?> createChannel(String authority, List<ClientInterceptor> interceptors,
80+
boolean mergeWithGlobalInterceptors) {
81+
Assert.notNull(interceptors, () -> "interceptors must not be null");
82+
return this.builders.computeIfAbsent(authority, path -> {
83+
ManagedChannelBuilder<?> builder = newChannelBuilder(this.targets.getTarget(path),
7584
this.credentials.getChannelCredentials(path));
85+
this.interceptorsConfigurer.configureInterceptors(builder, interceptors, mergeWithGlobalInterceptors);
7686
this.customizers.forEach((c) -> c.customize(path, builder));
77-
return builder;
87+
return new DisposableChannelBuilder(authority, builder);
7888
});
79-
return new DisposableChannelBuilder(authority, target);
8089
}
8190

8291
/**
@@ -86,15 +95,17 @@ public ManagedChannelBuilder<?> createChannel(String authority) {
8695
* @param creds the credentials for the channel
8796
* @return a new {@link ManagedChannelBuilder} for the given path and credentials
8897
*/
89-
protected ManagedChannelBuilder<?> newChannel(String path, ChannelCredentials creds) {
98+
protected ManagedChannelBuilder<?> newChannelBuilder(String path, ChannelCredentials creds) {
9099
return Grpc.newChannelBuilder(path, creds);
91100
}
92101

102+
private ManagedChannel buildAndRegisterChannel(String channelName, ManagedChannelBuilder<?> channelBuilder) {
103+
return this.channels.computeIfAbsent(channelName, (__) -> channelBuilder.build());
104+
}
105+
93106
@Override
94107
public void destroy() {
95-
for (ManagedChannel channel : this.channels.values()) {
96-
channel.shutdown();
97-
}
108+
this.channels.values().forEach(ManagedChannel::shutdown);
98109
}
99110

100111
/**
@@ -103,10 +114,10 @@ public void destroy() {
103114
*/
104115
class DisposableChannelBuilder extends ForwardingChannelBuilder2<DisposableChannelBuilder> {
105116

106-
private final ManagedChannelBuilder<?> delegate;
107-
108117
private final String authority;
109118

119+
private final ManagedChannelBuilder<?> delegate;
120+
110121
DisposableChannelBuilder(String authority, ManagedChannelBuilder<?> delegate) {
111122
this.authority = authority;
112123
this.delegate = delegate;
@@ -119,7 +130,7 @@ protected ManagedChannelBuilder<?> delegate() {
119130

120131
@Override
121132
public ManagedChannel build() {
122-
return DefaultGrpcChannelFactory.this.channels.computeIfAbsent(this.authority, name -> super.build());
133+
return DefaultGrpcChannelFactory.this.buildAndRegisterChannel(this.authority, this.delegate);
123134
}
124135

125136
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
* Partial copy from net.devh:grpc-spring-boot-starter.
17+
*/
18+
19+
package org.springframework.grpc.client;
20+
21+
import java.lang.annotation.Documented;
22+
import java.lang.annotation.ElementType;
23+
import java.lang.annotation.Retention;
24+
import java.lang.annotation.RetentionPolicy;
25+
import java.lang.annotation.Target;
26+
27+
import org.springframework.core.annotation.Order;
28+
29+
import io.grpc.ClientInterceptor;
30+
31+
/**
32+
* Annotation that can be specified on a gRPC {@link ClientInterceptor} bean which will
33+
* result in the interceptor being applied globally to channels.
34+
* <p>
35+
* The bean interceptor {@link Order} will be respected.
36+
*
37+
* @author Daniel Theuke ([email protected])
38+
* @author Chris Bono
39+
*/
40+
@Target({ ElementType.TYPE, ElementType.METHOD })
41+
@Retention(RetentionPolicy.RUNTIME)
42+
@Documented
43+
public @interface GlobalClientInterceptor {
44+
45+
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.grpc.client;
1818

19+
import java.util.List;
20+
21+
import io.grpc.ClientInterceptor;
1922
import io.grpc.ManagedChannelBuilder;
2023

2124
/**
@@ -34,4 +37,16 @@ public interface GrpcChannelFactory {
3437
*/
3538
ManagedChannelBuilder<?> createChannel(String authority);
3639

40+
/**
41+
* Creates a {@link ManagedChannelBuilder} for the given authority and the provided
42+
* client interceptors.
43+
* @param authority the target authority for the channel
44+
* @param interceptors the non-null list of interceptors to be applied to the channel
45+
* @param mergeWithGlobalInterceptors whether the provided interceptors should be
46+
* blended with the global interceptors.
47+
* @return a channel builder conifgured with the provided values
48+
*/
49+
ManagedChannelBuilder<?> createChannel(String authority, List<ClientInterceptor> interceptors,
50+
boolean mergeWithGlobalInterceptors);
51+
3752
}

0 commit comments

Comments
 (0)