Remove GrpcClientFactoryCustomizer

Existing APIs for customizing channel creation can be used instead.
This commit is contained in:
Dave Syer
2025-05-29 13:05:04 +01:00
parent c2a43c2f87
commit 8c27fe8ab7
7 changed files with 32 additions and 98 deletions

View File

@@ -1,15 +1,11 @@
package org.springframework.grpc.sample;
import io.grpc.Status.Code;
import io.grpc.StatusRuntimeException;
import io.grpc.reflection.v1.ServerReflectionGrpc;
import io.grpc.reflection.v1.ServerReflectionRequest;
import io.grpc.reflection.v1.ServerReflectionResponse;
import io.grpc.stub.StreamObserver;
import static org.junit.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.ObjectProvider;
@@ -23,8 +19,7 @@ import org.springframework.experimental.boot.server.exec.CommonsExecWebServerFac
import org.springframework.experimental.boot.server.exec.MavenClasspathEntry;
import org.springframework.experimental.boot.test.context.EnableDynamicProperty;
import org.springframework.experimental.boot.test.context.OAuth2ClientProviderIssuerUri;
import org.springframework.grpc.client.ChannelBuilderOptions;
import org.springframework.grpc.client.GrpcClientFactoryCustomizer;
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
import org.springframework.grpc.client.ImportGrpcClients;
import org.springframework.grpc.client.interceptor.security.BearerTokenAuthenticationInterceptor;
import org.springframework.grpc.sample.proto.HelloReply;
@@ -36,8 +31,12 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.test.annotation.DirtiesContext;
import static org.junit.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
import io.grpc.Status.Code;
import io.grpc.StatusRuntimeException;
import io.grpc.reflection.v1.ServerReflectionGrpc;
import io.grpc.reflection.v1.ServerReflectionRequest;
import io.grpc.reflection.v1.ServerReflectionResponse;
import io.grpc.stub.StreamObserver;
@SpringBootTest(properties = { "spring.grpc.server.port=0",
"spring.grpc.client.default-channel.address=static://0.0.0.0:${local.grpc.port}" })
@@ -129,9 +128,9 @@ public class GrpcServerApplicationTests {
}
@Bean
GrpcClientFactoryCustomizer stubs(ObjectProvider<ClientRegistrationRepository> context) {
return registry -> registry.channel("secure", ChannelBuilderOptions.defaults()
.withInterceptors(List.of(new BearerTokenAuthenticationInterceptor(() -> token(context)))));
GrpcChannelBuilderCustomizer<?> stubs(ObjectProvider<ClientRegistrationRepository> context) {
return GrpcChannelBuilderCustomizer.matching("secure",
builder -> builder.intercept(new BearerTokenAuthenticationInterceptor(() -> token(context))));
}
private String token(ObjectProvider<ClientRegistrationRepository> context) {

View File

@@ -3,7 +3,6 @@ package org.springframework.grpc.sample;
import static org.junit.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -15,8 +14,7 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.grpc.client.ChannelBuilderOptions;
import org.springframework.grpc.client.GrpcClientFactoryCustomizer;
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer;
import org.springframework.grpc.client.ImportGrpcClients;
import org.springframework.grpc.client.interceptor.security.BasicAuthenticationInterceptor;
import org.springframework.grpc.sample.proto.HelloReply;
@@ -113,9 +111,9 @@ public class GrpcServerApplicationTests {
static class ExtraConfiguration {
@Bean
GrpcClientFactoryCustomizer basicStubs() {
return registry -> registry.channel("secure", ChannelBuilderOptions.defaults()
.withInterceptors(List.of(new BasicAuthenticationInterceptor("user", "user"))));
GrpcChannelBuilderCustomizer<?> basicStubsCustomizer() {
return GrpcChannelBuilderCustomizer.matching("secure",
builder -> builder.intercept(new BasicAuthenticationInterceptor("user", "user")));
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.grpc.client;
import java.util.function.Consumer;
import io.grpc.ManagedChannelBuilder;
/**
@@ -54,4 +56,13 @@ public interface GrpcChannelBuilderCustomizer<T extends ManagedChannelBuilder<T>
};
}
static <T extends ManagedChannelBuilder<T>> GrpcChannelBuilderCustomizer<T> matching(String pattern,
Consumer<ManagedChannelBuilder<T>> consumer) {
return (authority, channel) -> {
if (pattern.matches(authority)) {
consumer.accept(channel);
}
};
}
}

View File

@@ -18,14 +18,12 @@ package org.springframework.grpc.client;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
@@ -42,14 +40,11 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import io.grpc.ManagedChannel;
import io.grpc.stub.AbstractStub;
/**
* A factory of gRPC clients that can be used to create client stubs as beans in an
* application context. The best way to interact with the factory is to declare a bean of
* type {@link GrpcClientFactoryCustomizer} in the application context. The customizer
* will be called with the factory before it is used to create the beans.
* application context.
*
* @author Dave Syer
*/
@@ -63,8 +58,6 @@ public class GrpcClientFactory {
private final ApplicationContext context;
private Map<String, Supplier<ManagedChannel>> options = new HashMap<>();
static {
DEFAULT_FACTORIES.add((Class<? extends StubFactory<?>>) BlockingStubFactory.class);
DEFAULT_FACTORIES.add((Class<? extends StubFactory<?>>) BlockingV2StubFactory.class);
@@ -80,25 +73,10 @@ public class GrpcClientFactory {
public <T> T getClient(String target, Class<T> type, Class<?> factory) {
@SuppressWarnings("unchecked")
StubFactory<T> stubs = (StubFactory<T>) findFactory(factory, type);
Supplier<ManagedChannel> channel = this.options.get(target);
if (channel == null) {
channel = () -> channels().createChannel(target, ChannelBuilderOptions.defaults());
}
Supplier<ManagedChannel> finalChannel = channel;
T client = (T) stubs.create(() -> finalChannel.get(), type);
T client = (T) stubs.create(() -> channels().createChannel(target, ChannelBuilderOptions.defaults()), type);
return client;
}
/**
* Register a channel factory for the given target. The channel will be created using
* the given options. If no options are provided, the default options will be used.
* @param target the name (or base url) of the target
* @param options the options to use to create the channel
*/
public void channel(String target, ChannelBuilderOptions options) {
this.options.put(target, () -> channels().createChannel(target, options));
}
private StubFactory<?> findFactory(Class<?> factoryType, Class<?> type) {
if (this.factories.isEmpty()) {
List<StubFactory<?>> factories = new ArrayList<>();

View File

@@ -1,34 +0,0 @@
/*
* 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 org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
/**
* Callback interface that can be implemented by beans wishing to customize the
* {@link GrpcClientFactory} before it is used. The registry is used by the application
* context very early in its lifecycle, so customizers should not refer directly to other
* beans. It is better to use a lazy lookup via the {@link ApplicationContext} or an
* {@link ObjectProvider}
*
* @author Dave Syer
*/
public interface GrpcClientFactoryCustomizer {
void customize(GrpcClientFactory registry);
}

View File

@@ -15,14 +15,9 @@
*/
package org.springframework.grpc.client;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
/**
* Post processor for {@link GrpcClientFactory} that applies the customizers and provides
@@ -44,12 +39,6 @@ public class GrpcClientFactoryPostProcessor implements ApplicationContextAware {
}
this.initialized = true;
this.registry = new GrpcClientFactory(context);
if (context.getBeanNamesForType(GrpcClientFactoryCustomizer.class).length > 0) {
List<GrpcClientFactoryCustomizer> values = new ArrayList<>(
context.getBeansOfType(GrpcClientFactoryCustomizer.class).values());
AnnotationAwareOrderComparator.sort(values);
values.forEach(customizer -> customizer.customize(this.registry));
}
}
<T> T getClient(String target, Class<T> type, Class<?> factory) {
@@ -59,12 +48,7 @@ public class GrpcClientFactoryPostProcessor implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (applicationContext instanceof GenericApplicationContext generic) {
this.context = generic;
}
else {
throw new IllegalStateException("ApplicationContext must be a GenericApplicationContext");
}
this.context = applicationContext;
}
}

View File

@@ -25,9 +25,7 @@ import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* Annotation to create gRPC client beans. If you want more control over the creation of
* the clients, or you don't want to use the annotation, you can use a bean of type
* {@link GrpcClientFactoryCustomizer} instead.
* Annotation to create gRPC client beans.
*
* @author Dave Syer
*/