Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.weaving.LoadTimeWeaverAware;
import org.springframework.instrument.classloading.LoadTimeWeaver;

import io.grpc.CompressorRegistry;
import io.grpc.DecompressorRegistry;
import io.grpc.NameResolverProvider;
import io.grpc.NameResolverRegistry;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.client.channelfactory.GrpcChannelConfigurer;
import net.devh.boot.grpc.client.channelfactory.GrpcChannelFactory;
Expand Down Expand Up @@ -149,8 +152,7 @@ List<GrpcChannelConfigurer> defaultChannelConfigurers() {
"io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder"})
@Bean
@Lazy
GrpcChannelFactory shadedNettyGrpcChannelFactory(
final GrpcChannelsProperties properties,
GrpcChannelFactory shadedNettyGrpcChannelFactory(final GrpcChannelsProperties properties,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert these unrelated formatting changes.

final GlobalClientInterceptorRegistry globalClientInterceptorRegistry,
final List<GrpcChannelConfigurer> channelConfigurers) {

Expand All @@ -167,8 +169,7 @@ GrpcChannelFactory shadedNettyGrpcChannelFactory(
@ConditionalOnClass(name = {"io.netty.channel.Channel", "io.grpc.netty.NettyChannelBuilder"})
@Bean
@Lazy
GrpcChannelFactory nettyGrpcChannelFactory(
final GrpcChannelsProperties properties,
GrpcChannelFactory nettyGrpcChannelFactory(final GrpcChannelsProperties properties,
final GlobalClientInterceptorRegistry globalClientInterceptorRegistry,
final List<GrpcChannelConfigurer> channelConfigurers) {

Expand All @@ -184,13 +185,25 @@ GrpcChannelFactory nettyGrpcChannelFactory(
@ConditionalOnMissingBean(GrpcChannelFactory.class)
@Bean
@Lazy
GrpcChannelFactory inProcessGrpcChannelFactory(
final GrpcChannelsProperties properties,
GrpcChannelFactory inProcessGrpcChannelFactory(final GrpcChannelsProperties properties,
final GlobalClientInterceptorRegistry globalClientInterceptorRegistry,
final List<GrpcChannelConfigurer> channelConfigurers) {

log.warn("Could not find a GrpcChannelFactory on the classpath: Creating InProcessChannelFactory as fallback");
return new InProcessChannelFactory(properties, globalClientInterceptorRegistry, channelConfigurers);
}

@Configuration(proxyBeanMethods = false)
static class GrpcClientConstructorInjectionConfiguration implements LoadTimeWeaverAware {
@Autowired
private GrpcClientBeanPostProcessor grpcClientBeanPostProcessor;

@PostConstruct
public void init() {
grpcClientBeanPostProcessor.initGrpClientConstructorInjections();
}

@Override
public void setLoadTimeWeaver(LoadTimeWeaver ltw) {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,52 @@

import static java.util.Objects.requireNonNull;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.InvalidPropertyException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import com.google.common.collect.Lists;

import io.grpc.Channel;
import io.grpc.ClientInterceptor;
import io.grpc.stub.AbstractStub;
import jakarta.annotation.PostConstruct;
import net.devh.boot.grpc.client.channelfactory.GrpcChannelFactory;
import net.devh.boot.grpc.client.nameresolver.NameResolverRegistration;
import net.devh.boot.grpc.client.stubfactory.FallbackStubFactory;
Expand All @@ -57,7 +76,8 @@
* @author Michael ([email protected])
* @author Daniel Theuke ([email protected])
*/
public class GrpcClientBeanPostProcessor implements BeanPostProcessor {
public class GrpcClientBeanPostProcessor
implements InstantiationAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor {

private final ApplicationContext applicationContext;

Expand All @@ -70,25 +90,26 @@ public class GrpcClientBeanPostProcessor implements BeanPostProcessor {
// For bean registration via @GrpcClientBean
private ConfigurableListableBeanFactory configurableBeanFactory;

private final Set<Class<? extends Annotation>> grpcClientAnnotationTypes = new LinkedHashSet<>(4);

private final Map<String, InjectionMetadata> injectionMetadataCache = new ConcurrentHashMap<>(256);

/**
* Creates a new GrpcClientBeanPostProcessor with the given ApplicationContext.
* Creates a new GrpcClientBeanPostProcessor with the given ApplicationContext for GrpcClient standard
* {@link GrpcClient @GrpcClient} annotation.
*
* @param applicationContext The application context that will be used to get lazy access to the
* {@link GrpcChannelFactory} and {@link StubTransformer}s.
*/
public GrpcClientBeanPostProcessor(final ApplicationContext applicationContext) {
this.applicationContext = requireNonNull(applicationContext, "applicationContext");
}

@PostConstruct
public void init() {
initGrpClientConstructorInjections();
this.grpcClientAnnotationTypes.add(GrpcClient.class);
}

/**
* Triggers registering grpc client beans from GrpcClientConstructorInjection.
*/
private void initGrpClientConstructorInjections() {
public void initGrpClientConstructorInjections() {
Iterable<GrpcClientConstructorInjection.Registry> registries;
try {
registries = getConfigurableBeanFactory().getBean(GrpcClientConstructorInjection.class).getRegistries();
Expand Down Expand Up @@ -120,9 +141,6 @@ private void initGrpClientConstructorInjections() {
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
Class<?> clazz = bean.getClass();
do {
processFields(clazz, bean);
processMethods(clazz, bean);

if (isAnnotatedWithConfiguration(clazz)) {
processGrpcClientBeansAnnotations(clazz);
}
Expand All @@ -132,6 +150,19 @@ public Object postProcessBeforeInitialization(final Object bean, final String be
return bean;
}

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findGrpcClientMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
} catch (BeanCreationException ex) {
throw ex;
} catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of gRPC client stub failed", ex);
}
return pvs;
}

/**
* Processes the bean's fields in the given class.
*
Expand Down Expand Up @@ -398,4 +429,110 @@ private boolean isAnnotatedWithConfiguration(final Class<?> clazz) {
return configurationAnnotation != null;
}

@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
InjectionMetadata metadata = findGrpcClientMetadata(beanName, beanType, null);
metadata.checkConfigMembers(beanDefinition);
}

private InjectionMetadata findGrpcClientMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
// Fall back to class name as cache key, for backwards compatibility with custom callers.
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
// Quick check on the concurrent map first, with minimal locking.
InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
synchronized (this.injectionMetadataCache) {
metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
if (metadata != null) {
metadata.clear(pvs);
}
metadata = buildGrpcClientMetadata(clazz);
this.injectionMetadataCache.put(cacheKey, metadata);
}
}
}
return metadata;
}

private InjectionMetadata buildGrpcClientMetadata(Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, this.grpcClientAnnotationTypes)) {
return InjectionMetadata.EMPTY;
}

List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;

do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

ReflectionUtils.doWithLocalFields(targetClass, field -> {
MergedAnnotation<?> ann = findGrpcClientAnnotation(field);
if (ann != null) {
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException(
"GrpcClient annotation is not supported on static fields: " + field);
}
currElements.add(new GrpcClientMemberElement(field, null));
}
});

ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
MergedAnnotation<?> ann = findGrpcClientAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException(
"GrpcClient annotation is not supported on static method: " + method);
}
if (method.getParameterCount() == 0) {
throw new IllegalStateException(
"GrpcClient annotation should only be used on methods with parameters: " + method);
}
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new GrpcClientMemberElement(method, pd));
}
});

elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
} while (targetClass != null && targetClass != Object.class);

return InjectionMetadata.forElements(elements, clazz);
}

private MergedAnnotation<?> findGrpcClientAnnotation(AccessibleObject ao) {
MergedAnnotations annotations = MergedAnnotations.from(ao);
for (Class<? extends Annotation> type : this.grpcClientAnnotationTypes) {
MergedAnnotation<?> annotation = annotations.get(type);
if (annotation.isPresent()) {
return annotation;
}
}
return null;
}

/**
* Class representing injection information about an annotated member.
*/
private class GrpcClientMemberElement extends InjectionMetadata.InjectedElement {

public GrpcClientMemberElement(Member member, @Nullable PropertyDescriptor pd) {
super(member, pd);
}

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Class<?> clazz = bean.getClass();
do {
processFields(clazz, bean);
processMethods(clazz, bean);

clazz = clazz.getSuperclass();
} while (clazz != null);
}
}
}
Loading