Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix metric disable when using the constructor injection of the Grpc Client #907

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
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