Split bean registration and creation for stubs
GrpcClientRegistry was responsible for both, which causes lifecycle issues when users don't follow recommendations. This change pushes the bean registration firmly down a level into an ImportBeanDefinitionRegistrar. Also helps with AOT because the AOT processor only runs the IBDR at build time.
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.grpc.client.GrpcClientFactory.GrpcClientRegistrationSpec;
|
||||
|
||||
public abstract class AbstractGrpcClientRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
@Override
|
||||
public final void registerBeanDefinitions(AnnotationMetadata meta, BeanDefinitionRegistry registry) {
|
||||
GrpcClientRegistrationSpec[] specs = collect(meta);
|
||||
String name = GrpcClientFactoryPostProcessor.class.getName();
|
||||
for (GrpcClientRegistrationSpec spec : specs) {
|
||||
GrpcClientFactory.register(registry, spec);
|
||||
}
|
||||
if (!registry.containsBeanDefinition(name)) {
|
||||
registry.registerBeanDefinition(name, new RootBeanDefinition(GrpcClientFactoryPostProcessor.class));
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract GrpcClientRegistrationSpec[] collect(AnnotationMetadata meta);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.grpc.client.GrpcClientFactory.GrpcClientRegistrationSpec;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
public class AnnotationGrpcClientRegistrar extends AbstractGrpcClientRegistrar {
|
||||
|
||||
@Override
|
||||
protected GrpcClientRegistrationSpec[] collect(AnnotationMetadata meta) {
|
||||
Set<AnnotationAttributes> attrs = meta.getMergedRepeatableAnnotationAttributes(ImportGrpcClients.class,
|
||||
ImportGrpcClients.Container.class, false);
|
||||
Set<GrpcClientRegistrationSpec> specs = new HashSet<>();
|
||||
for (AnnotationAttributes attr : attrs) {
|
||||
specs.add(register(meta, attr));
|
||||
}
|
||||
return specs.toArray(new GrpcClientRegistrationSpec[0]);
|
||||
}
|
||||
|
||||
private GrpcClientRegistrationSpec register(AnnotationMetadata meta, AnnotationAttributes attr) {
|
||||
String target = attr.getString("target");
|
||||
String prefix = attr.getString("prefix");
|
||||
Class<?>[] types = attr.getClassArray("types");
|
||||
Class<?>[] basePackageClasses = attr.getClassArray("basePackageClasses");
|
||||
String[] basePackages = attr.getStringArray("basePackages");
|
||||
Class<? extends StubFactory<?>> factory = attr.getClass("factory");
|
||||
if (factory == UnspecifiedStubFactory.class) {
|
||||
factory = null;
|
||||
}
|
||||
if (types.length == 0 && basePackageClasses.length == 0 && basePackages.length == 0) {
|
||||
basePackages = new String[] { ClassUtils.getPackageName(meta.getClassName()) };
|
||||
}
|
||||
return GrpcClientRegistrationSpec.of(target)
|
||||
.factory(factory)
|
||||
.types(types)
|
||||
.packages(basePackages)
|
||||
.packageClasses(basePackageClasses)
|
||||
.prefix(prefix);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,107 +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 java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
public class GrpcClientConfiguration implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata meta, BeanDefinitionRegistry registry) {
|
||||
Set<AnnotationAttributes> attrs = meta.getMergedRepeatableAnnotationAttributes(ImportGrpcClients.class,
|
||||
ImportGrpcClients.Container.class, false);
|
||||
for (AnnotationAttributes attr : attrs) {
|
||||
register(registry, meta, attr, meta.getClassName() + ".");
|
||||
}
|
||||
String name = GrpcClientRegistryPostProcessor.class.getName();
|
||||
if (!registry.containsBeanDefinition(name)) {
|
||||
registry.registerBeanDefinition(name, new RootBeanDefinition(GrpcClientRegistryPostProcessor.class));
|
||||
}
|
||||
}
|
||||
|
||||
private void register(BeanDefinitionRegistry registry, AnnotationMetadata meta, AnnotationAttributes attr,
|
||||
String stem) {
|
||||
String target = attr.getString("target");
|
||||
String prefix = attr.getString("prefix");
|
||||
Class<?>[] types = attr.getClassArray("types");
|
||||
Class<? extends StubFactory<?>> factory = attr.getClass("factory");
|
||||
Class<?>[] basePackageClasses = attr.getClassArray("basePackageClasses");
|
||||
String[] basePackages = attr.getStringArray("basePackages");
|
||||
if (types.length == 0 && basePackageClasses.length == 0 && basePackages.length == 0) {
|
||||
basePackages = new String[] { ClassUtils.getPackageName(meta.getClassName()) };
|
||||
}
|
||||
register(registry, stem, target, prefix, types, factory, basePackageClasses, basePackages);
|
||||
}
|
||||
|
||||
private void register(BeanDefinitionRegistry registry, String stem, String target, String prefix, Class<?>[] types,
|
||||
Class<? extends StubFactory<?>> factory, Class<?>[] basePackageClasses, String[] basePackages) {
|
||||
RootBeanDefinition beanDef = new RootBeanDefinition(SimpleGrpcClientRegistryCustomizer.class);
|
||||
beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
String name = target;
|
||||
beanDef.setBeanClass(SimpleGrpcClientRegistryCustomizer.class);
|
||||
ConstructorArgumentValues values = beanDef.getConstructorArgumentValues();
|
||||
values.addGenericArgumentValue(name);
|
||||
values.addGenericArgumentValue(prefix);
|
||||
values.addGenericArgumentValue(types);
|
||||
values.addGenericArgumentValue(factory);
|
||||
values.addGenericArgumentValue(basePackageClasses);
|
||||
values.addGenericArgumentValue(basePackages);
|
||||
registry.registerBeanDefinition(stem + target, beanDef);
|
||||
}
|
||||
|
||||
static class SimpleGrpcClientRegistryCustomizer implements GrpcClientRegistryCustomizer {
|
||||
|
||||
private String target;
|
||||
|
||||
private Class<?>[] types;
|
||||
|
||||
private String prefix;
|
||||
|
||||
private Class<?>[] basePackageClasses;
|
||||
|
||||
private Class<? extends StubFactory<?>> type;
|
||||
|
||||
private String[] basePackages;
|
||||
|
||||
SimpleGrpcClientRegistryCustomizer(String target, String prefix, Class<?>[] types,
|
||||
Class<? extends StubFactory<?>> type, Class<?>[] basePackageClasses, String[] basePackages) {
|
||||
this.target = target;
|
||||
this.prefix = prefix;
|
||||
this.types = types;
|
||||
this.type = type;
|
||||
this.basePackageClasses = basePackageClasses;
|
||||
this.basePackages = basePackages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(GrpcClientRegistry registry) {
|
||||
registry.channel(this.target).prefix(this.prefix).register(this.types);
|
||||
registry.channel(this.target).prefix(this.prefix).scan(this.type).packageClasses(this.basePackageClasses);
|
||||
registry.channel(this.target).prefix(this.prefix).scan(this.type).packages(this.basePackages);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.grpc.internal.ClasspathScanner;
|
||||
import org.springframework.util.ClassUtils;
|
||||
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.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class GrpcClientFactory {
|
||||
|
||||
private static Map<Class<?>, StubFactory<?>> FACTORIES = new HashMap<>();
|
||||
|
||||
private final ApplicationContext context;
|
||||
|
||||
private Map<String, Supplier<ManagedChannel>> options = new HashMap<>();
|
||||
|
||||
static {
|
||||
stubs(new BlockingStubFactory());
|
||||
stubs(new BlockingV2StubFactory());
|
||||
stubs(new FutureStubFactory());
|
||||
stubs(new ReactorStubFactory());
|
||||
stubs(new SimpleStubFactory());
|
||||
SpringFactoriesLoader.loadFactories(StubFactory.class, GrpcClientFactory.class.getClassLoader())
|
||||
.forEach(GrpcClientFactory::stubs);
|
||||
}
|
||||
|
||||
public GrpcClientFactory(ApplicationContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public <T extends AbstractStub<T>> T getClient(String target, Class<T> type, Class<?> factory) {
|
||||
StubFactory<?> stubs = findFactory(factory, type);
|
||||
Supplier<ManagedChannel> channel = this.options.get(target);
|
||||
if (channel == null) {
|
||||
channel = () -> channels().createChannel(target, ChannelBuilderOptions.defaults());
|
||||
}
|
||||
Supplier<ManagedChannel> finalChannel = channel;
|
||||
@SuppressWarnings("unchecked")
|
||||
T client = (T) stubs.create(() -> finalChannel.get(), 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 static void stubs(StubFactory<? extends AbstractStub<?>> factory) {
|
||||
FACTORIES.put(factory.getClass(), factory);
|
||||
}
|
||||
|
||||
public static StubFactory<?> findFactory(Class<?> type) {
|
||||
return findFactory(null, type);
|
||||
}
|
||||
|
||||
private static StubFactory<?> findFactory(Class<?> factoryType, Class<?> type) {
|
||||
StubFactory<? extends AbstractStub<?>> factory = null;
|
||||
if (factoryType != null && factoryType != UnspecifiedStubFactory.class) {
|
||||
factory = FACTORIES.get(factoryType);
|
||||
if (!factory.supports(type)) {
|
||||
factory = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
List<StubFactory<?>> factories = new ArrayList<>(FACTORIES.values());
|
||||
AnnotationAwareOrderComparator.sort(factories);
|
||||
for (StubFactory<? extends AbstractStub<?>> value : factories) {
|
||||
if (value.supports(type)) {
|
||||
factory = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return factory;
|
||||
}
|
||||
|
||||
private GrpcChannelFactory channels() {
|
||||
return this.context.getBean(GrpcChannelFactory.class);
|
||||
}
|
||||
|
||||
public static void register(BeanDefinitionRegistry registry, GrpcClientRegistrationSpec spec) {
|
||||
for (Class<?> type : spec.types()) {
|
||||
StubFactory<?> factory = GrpcClientFactory.findFactory(spec.factory(), type);
|
||||
if (factory == null) {
|
||||
continue;
|
||||
}
|
||||
RootBeanDefinition beanDef = (RootBeanDefinition) BeanDefinitionBuilder.rootBeanDefinition(type)
|
||||
.setLazyInit(true)
|
||||
.setFactoryMethodOnBean("getClient", GrpcClientFactoryPostProcessor.class.getName())
|
||||
.addConstructorArgValue(spec.target())
|
||||
.addConstructorArgValue(type)
|
||||
.addConstructorArgValue(spec.factory())
|
||||
.getBeanDefinition();
|
||||
beanDef.setTargetType(type);
|
||||
String beanName = StringUtils.hasText(spec.prefix()) ? spec.prefix() + type.getSimpleName()
|
||||
: StringUtils.uncapitalize(type.getSimpleName());
|
||||
if (!registry.containsBeanDefinition(beanName)) {
|
||||
// Avoid registering the same bean definition multiple times
|
||||
registry.registerBeanDefinition(beanName, beanDef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record GrpcClientRegistrationSpec(String prefix, Class<? extends StubFactory<?>> factory, String target,
|
||||
Class<?>[] types) {
|
||||
|
||||
private static ClasspathScanner SCANNER = new ClasspathScanner();
|
||||
|
||||
public static GrpcClientRegistrationSpec defaults() {
|
||||
return new GrpcClientRegistrationSpec("default", new Class[0]);
|
||||
}
|
||||
|
||||
public static GrpcClientRegistrationSpec of(String target) {
|
||||
return new GrpcClientRegistrationSpec(target, new Class[0]);
|
||||
}
|
||||
|
||||
public GrpcClientRegistrationSpec(String target, Class<?>[] types) {
|
||||
this("", UnspecifiedStubFactory.class, target, types);
|
||||
}
|
||||
|
||||
public GrpcClientRegistrationSpec(String prefix, String target, Class<?>[] types) {
|
||||
this(prefix, UnspecifiedStubFactory.class, target, types);
|
||||
}
|
||||
|
||||
public GrpcClientRegistrationSpec factory(Class<? extends StubFactory<?>> factory) {
|
||||
return new GrpcClientRegistrationSpec(this.prefix, factory, this.target, this.types);
|
||||
}
|
||||
|
||||
public GrpcClientRegistrationSpec types(Class<?>... types) {
|
||||
return new GrpcClientRegistrationSpec(this.prefix, this.factory, this.target, types);
|
||||
}
|
||||
|
||||
public GrpcClientRegistrationSpec prefix(String prefix) {
|
||||
if (StringUtils.hasText(prefix)) {
|
||||
return new GrpcClientRegistrationSpec(prefix, this.factory, this.target, this.types);
|
||||
}
|
||||
else {
|
||||
return new GrpcClientRegistrationSpec("", this.factory, this.target, this.types);
|
||||
}
|
||||
}
|
||||
|
||||
public GrpcClientRegistrationSpec packages(String... packages) {
|
||||
Set<Class<?>> allTypes = new HashSet<>();
|
||||
allTypes.addAll(Set.of(this.types));
|
||||
for (String basePackage : packages) {
|
||||
// TODO: find a global factory default if scanning and only add the types
|
||||
// that it supports
|
||||
allTypes.addAll(SCANNER.scan(basePackage, AbstractStub.class));
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends AbstractStub<?>>[] newTypes = allTypes.toArray(new Class[0]);
|
||||
return new GrpcClientRegistrationSpec(this.prefix, this.factory, this.target, newTypes);
|
||||
}
|
||||
|
||||
public GrpcClientRegistrationSpec packageClasses(Class<?>... packageClasses) {
|
||||
String[] packages = new String[packageClasses.length];
|
||||
for (Class<?> basePackageClass : packageClasses) {
|
||||
for (int i = 0; i < packageClasses.length; i++) {
|
||||
String basePackage = ClassUtils.getPackageName(basePackageClass);
|
||||
packages[i] = basePackage;
|
||||
}
|
||||
}
|
||||
return packages(packages);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,15 +20,15 @@ import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* Callback interface that can be implemented by beans wishing to customize the
|
||||
* {@link GrpcClientRegistry} before it is used. The registry is used by the application
|
||||
* {@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 GrpcClientRegistryCustomizer {
|
||||
public interface GrpcClientFactoryCustomizer {
|
||||
|
||||
void customize(GrpcClientRegistry registry);
|
||||
void customize(GrpcClientFactory registry);
|
||||
|
||||
}
|
||||
@@ -19,41 +19,44 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
|
||||
public class GrpcClientRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware {
|
||||
import io.grpc.stub.AbstractStub;
|
||||
|
||||
private GenericApplicationContext context;
|
||||
/**
|
||||
* Post processor for {@link GrpcClientFactory} that applies the customizers and provides
|
||||
* a factory for client instances at runtime.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class GrpcClientFactoryPostProcessor implements ApplicationContextAware {
|
||||
|
||||
private ApplicationContext context;
|
||||
|
||||
private boolean initialized = false;
|
||||
|
||||
private GrpcClientRegistry registry;
|
||||
private GrpcClientFactory registry;
|
||||
|
||||
private void initialize(GenericApplicationContext context) {
|
||||
if (this.initialized) {
|
||||
private void initialize(ApplicationContext context) {
|
||||
if (this.initialized || this.context == null) {
|
||||
return;
|
||||
}
|
||||
this.initialized = true;
|
||||
this.registry = new GrpcClientRegistry(context);
|
||||
if (context.getBeanNamesForType(GrpcClientRegistryCustomizer.class).length > 0) {
|
||||
List<GrpcClientRegistryCustomizer> values = new ArrayList<>(
|
||||
context.getBeansOfType(GrpcClientRegistryCustomizer.class).values());
|
||||
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));
|
||||
}
|
||||
this.registry.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
if (this.context != null) {
|
||||
initialize(this.context);
|
||||
}
|
||||
<T extends AbstractStub<T>> T getClient(String target, Class<T> type, Class<?> factory) {
|
||||
initialize(this.context);
|
||||
return this.registry.getClient(target, (Class<T>) type, factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,296 +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 java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.grpc.internal.ClasspathScanner;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import io.grpc.Channel;
|
||||
import io.grpc.ManagedChannel;
|
||||
import io.grpc.stub.AbstractStub;
|
||||
|
||||
/**
|
||||
* A registry of gRPC clients that can be used to register client stubs as beans in an
|
||||
* application context. The best way to interact with the registry is to declare a bean of
|
||||
* type {@link GrpcClientRegistryCustomizer} in the application context. The customizer
|
||||
* will be called with the registry before it is used to register the beans.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class GrpcClientRegistry {
|
||||
|
||||
private List<StubFactory<?>> factories = new ArrayList<>();
|
||||
|
||||
private Map<Class<?>, StubFactory<?>> factoriesByClass = new HashMap<>();
|
||||
|
||||
private Map<String, DeferredBeanDefinition<?>> beans = new HashMap<>();
|
||||
|
||||
private final GenericApplicationContext context;
|
||||
|
||||
public GrpcClientRegistry(GenericApplicationContext context) {
|
||||
this.context = context;
|
||||
stubs(new BlockingStubFactory());
|
||||
stubs(new BlockingV2StubFactory());
|
||||
stubs(new FutureStubFactory());
|
||||
stubs(new ReactorStubFactory());
|
||||
stubs(new SimpleStubFactory());
|
||||
SpringFactoriesLoader.loadFactories(StubFactory.class, getClass().getClassLoader()).forEach(this::stubs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called internally to register the beans in the application context.
|
||||
*/
|
||||
void close() {
|
||||
for (Map.Entry<String, DeferredBeanDefinition<?>> entry : this.beans.entrySet()) {
|
||||
registerBean(entry.getKey(), entry.getValue().type(), entry.getValue().supplier());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> void registerBean(String key, Class<?> type, Supplier<?> supplier) {
|
||||
Supplier<T> real = (Supplier<T>) supplier;
|
||||
Class<T> stub = (Class<T>) type;
|
||||
this.context.registerBean(key, stub, real, bd -> {
|
||||
bd.setLazyInit(true);
|
||||
bd.setAttribute(BeanRegistrationAotProcessor.IGNORE_REGISTRATION_ATTRIBUTE, true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a stub factory. All stub factories are consulted (in {@link Ordered}
|
||||
* order) until one is found that accepts the stub type being considered.
|
||||
* @param factory the stub factory to register
|
||||
* @return this
|
||||
*/
|
||||
public GrpcClientRegistry stubs(StubFactory<? extends AbstractStub<?>> factory) {
|
||||
if (this.factoriesByClass.containsKey(factory.getClass())) {
|
||||
this.factories.remove(this.factoriesByClass.get(factory.getClass()));
|
||||
}
|
||||
this.factories.add(factory);
|
||||
this.factoriesByClass.put(factory.getClass(), factory);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a group of clients that share a common channel.
|
||||
* @param name the name of the channel
|
||||
* @return a group of clients to be configured
|
||||
*/
|
||||
public GrpcClientGroup channel(String name) {
|
||||
return channel(name, ChannelBuilderOptions.defaults());
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a group of clients that share a common channel.
|
||||
* @param name the name of the channel
|
||||
* @param options the builder options to use when the channel is created
|
||||
* @return a group of clients to be configured
|
||||
*/
|
||||
public GrpcClientGroup channel(String name, ChannelBuilderOptions options) {
|
||||
return new GrpcClientGroup(() -> channels().createChannel(name, options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a group of clients that share a common channel.
|
||||
* @param channel a factory for the channel
|
||||
* @return a group of clients to be configured
|
||||
*/
|
||||
public GrpcClientGroup channel(Supplier<ManagedChannel> channel) {
|
||||
return new GrpcClientGroup(channel);
|
||||
}
|
||||
|
||||
private <T extends AbstractStub<?>> void preRegisterBean(String beanName, Class<T> type,
|
||||
Supplier<T> clientFactory) {
|
||||
this.beans.put(beanName, new DeferredBeanDefinition<>(type, clientFactory));
|
||||
}
|
||||
|
||||
private <T extends AbstractStub<?>> void preRegisterType(String beanName, Supplier<ManagedChannel> channel,
|
||||
Class<? extends StubFactory<?>> factoryType, Class<T> type) {
|
||||
StubFactory<? extends AbstractStub<?>> factory = null;
|
||||
if (factoryType != null) {
|
||||
factory = this.factoriesByClass.get(factoryType);
|
||||
if (!factory.supports(type)) {
|
||||
factory = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
AnnotationAwareOrderComparator.sort(this.factories);
|
||||
for (StubFactory<? extends AbstractStub<?>> value : this.factories) {
|
||||
if (value.supports(type)) {
|
||||
factory = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (factory != null) {
|
||||
StubFactory<? extends AbstractStub<?>> value = factory;
|
||||
this.beans.put(beanName, new DeferredBeanDefinition<>(type, () -> type.cast(value.create(channel, type))));
|
||||
return;
|
||||
}
|
||||
// Ignore unsupported types
|
||||
}
|
||||
|
||||
private GrpcChannelFactory channels() {
|
||||
return this.context.getBean(GrpcChannelFactory.class);
|
||||
}
|
||||
|
||||
private static record DeferredBeanDefinition<T extends AbstractStub<?>>(Class<T> type, Supplier<T> supplier) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A group of gRPC clients that share a common channel. You can use this group to scan
|
||||
* for stubs or to register individual stubs. Each stub will be created as a bean when
|
||||
* the application context refreshes so you can inject it into application code by
|
||||
* type (if unique) or by name. The bean names are determined by a concatenation of
|
||||
* the {@link GrpcClientGroup#prefix(String) prefix} and the simple name of the stub
|
||||
* class.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public final class GrpcClientGroup {
|
||||
|
||||
private ClasspathScanner scanner = new ClasspathScanner();
|
||||
|
||||
private final Supplier<ManagedChannel> channel;
|
||||
|
||||
private String prefix = "";
|
||||
|
||||
private Class<? extends StubFactory<?>> factory;
|
||||
|
||||
private GrpcClientGroup(Supplier<ManagedChannel> channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a stub with the given type. The stub will be created using the given
|
||||
* factory.
|
||||
* @param <T> the parametric type of the stub
|
||||
* @param type the type of the stub
|
||||
* @param factory the factory to use to create the stub
|
||||
* @return the parent registry
|
||||
*/
|
||||
public <T extends AbstractStub<?>> GrpcClientRegistry register(Class<T> type, Function<Channel, T> factory) {
|
||||
String beanName = type.getSimpleName();
|
||||
if (StringUtils.hasText(this.prefix)) {
|
||||
beanName = this.prefix + beanName;
|
||||
}
|
||||
else {
|
||||
beanName = StringUtils.uncapitalize(beanName);
|
||||
}
|
||||
preRegisterBean(beanName, type, () -> factory.apply(this.channel.get()));
|
||||
return GrpcClientRegistry.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register stubs with the given types. The stub will be created using the given
|
||||
* factory.
|
||||
* @param <T> ignore this
|
||||
* @param types the types of the stubs
|
||||
* @return the parent registry
|
||||
*/
|
||||
public <T extends AbstractStub<?>> GrpcClientRegistry register(Class<?>... types) {
|
||||
for (Class<?> type : types) {
|
||||
String beanName = type.getSimpleName();
|
||||
if (StringUtils.hasText(this.prefix)) {
|
||||
beanName = this.prefix + beanName;
|
||||
}
|
||||
else {
|
||||
beanName = StringUtils.uncapitalize(beanName);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<T> stub = (Class<T>) type;
|
||||
preRegisterType(beanName, this.channel, this.factory, stub);
|
||||
}
|
||||
return GrpcClientRegistry.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register stubs by scanning the packages (and subpackages) of the provided
|
||||
* types. The stubs will be created using the factory from
|
||||
* {@link GrpcClientGroup#factory factory()} if provided, otherwise a factory will
|
||||
* be selected based on the stub type. N.B. it is best to provide a factory if you
|
||||
* can, so that the correct stubs are created, otherwise you might get more beans
|
||||
* than you need.
|
||||
* @param basePackageClasses the base types of the stub packages
|
||||
* @return the parent registry
|
||||
*/
|
||||
public GrpcClientRegistry packageClasses(Class<?>... basePackageClasses) {
|
||||
String[] basePackages = new String[basePackageClasses.length];
|
||||
for (int i = 0; i < basePackageClasses.length; i++) {
|
||||
basePackages[i] = ClassUtils.getPackageName(basePackageClasses[i]);
|
||||
}
|
||||
return packages(basePackages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register stubs by scanning the provided packages (and subpackages). The stubs
|
||||
* will be created using the factory from {@link GrpcClientGroup#factory
|
||||
* factory()} if provided, otherwise a factory will be selected based on the stub
|
||||
* type. N.B. it is best to provide a factory if you can, so that the correct
|
||||
* stubs are created, otherwise you might get more beans than you need.
|
||||
* @param basePackages the base packages of the stubs
|
||||
* @return the parent registry
|
||||
*/
|
||||
|
||||
public GrpcClientRegistry packages(String... basePackages) {
|
||||
for (String basePackage : basePackages) {
|
||||
for (Class<?> stub : this.scanner.scan(basePackage, AbstractStub.class)) {
|
||||
register(stub);
|
||||
}
|
||||
}
|
||||
return GrpcClientRegistry.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to scan for stubs using the given factory.
|
||||
* @param <T> the generic type of the factory
|
||||
* @param factory the factory type
|
||||
* @return this
|
||||
*/
|
||||
public <T extends StubFactory<?>> GrpcClientGroup scan(Class<T> factory) {
|
||||
GrpcClientGroup group = new GrpcClientGroup(this.channel);
|
||||
group.prefix = this.prefix;
|
||||
group.factory = factory;
|
||||
return group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a prefix for the generated bean definitions.
|
||||
* @param prefix the provided prefix
|
||||
* @return this
|
||||
*/
|
||||
public GrpcClientGroup prefix(String prefix) {
|
||||
GrpcClientGroup group = new GrpcClientGroup(this.channel);
|
||||
group.prefix = prefix;
|
||||
return group;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,14 +29,14 @@ import io.grpc.stub.AbstractStub;
|
||||
/**
|
||||
* 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 GrpcClientRegistryCustomizer} instead.
|
||||
* {@link GrpcClientFactoryCustomizer} instead.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Import(GrpcClientConfiguration.class)
|
||||
@Import(AnnotationGrpcClientRegistrar.class)
|
||||
@Repeatable(ImportGrpcClients.Container.class)
|
||||
public @interface ImportGrpcClients {
|
||||
|
||||
@@ -67,7 +67,7 @@ public @interface ImportGrpcClients {
|
||||
* basePackageClasses or basePackages) and you need to customize the stub creation.
|
||||
* @return the factory type, default is {@link BlockingStubFactory}
|
||||
*/
|
||||
Class<? extends StubFactory<?>> factory() default BlockingStubFactory.class;
|
||||
Class<? extends StubFactory<?>> factory() default UnspecifiedStubFactory.class;
|
||||
|
||||
/**
|
||||
* The base package classes to scan for annotated components. If not specified,
|
||||
@@ -87,7 +87,7 @@ public @interface ImportGrpcClients {
|
||||
@Documented
|
||||
// In case there is more than one @ImportGrpcClients annotation we need to
|
||||
// import here as well:
|
||||
@Import(GrpcClientConfiguration.class)
|
||||
@Import(AnnotationGrpcClientRegistrar.class)
|
||||
@interface Container {
|
||||
|
||||
ImportGrpcClients[] value() default {};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 io.grpc.stub.AbstractStub;
|
||||
|
||||
public interface UnspecifiedStubFactory extends StubFactory<AbstractStub<?>> {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user