Olga Maciaszek-Sharma
2 years ago
3 changed files with 169 additions and 111 deletions
@ -0,0 +1,149 @@ |
|||||||
|
package org.springframework.cloud.openfeign.aot; |
||||||
|
|
||||||
|
import java.util.Map; |
||||||
|
import java.util.Objects; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
import javax.lang.model.element.Modifier; |
||||||
|
|
||||||
|
import org.springframework.aot.generate.GenerationContext; |
||||||
|
import org.springframework.aot.generate.MethodReference; |
||||||
|
import org.springframework.beans.factory.FactoryBean; |
||||||
|
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; |
||||||
|
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; |
||||||
|
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; |
||||||
|
import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter; |
||||||
|
import org.springframework.beans.factory.config.BeanDefinition; |
||||||
|
import org.springframework.beans.factory.config.BeanDefinitionHolder; |
||||||
|
import org.springframework.beans.factory.config.ConfigurableBeanFactory; |
||||||
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; |
||||||
|
import org.springframework.beans.factory.support.AbstractBeanDefinition; |
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionBuilder; |
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; |
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry; |
||||||
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||||
|
import org.springframework.beans.factory.support.GenericBeanDefinition; |
||||||
|
import org.springframework.beans.factory.support.RegisteredBean; |
||||||
|
import org.springframework.cloud.openfeign.FeignClientFactory; |
||||||
|
import org.springframework.cloud.openfeign.FeignClientFactoryBean; |
||||||
|
import org.springframework.cloud.openfeign.FeignClientSpecification; |
||||||
|
import org.springframework.context.support.GenericApplicationContext; |
||||||
|
import org.springframework.javapoet.MethodSpec; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import org.springframework.util.ClassUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Olga Maciaszek-Sharma |
||||||
|
*/ |
||||||
|
public class FeignClientBeanFactoryInitializationAotProcessor implements BeanRegistrationExcludeFilter, BeanFactoryInitializationAotProcessor { |
||||||
|
|
||||||
|
|
||||||
|
private final GenericApplicationContext context; |
||||||
|
|
||||||
|
private final Map<String, BeanDefinition> feignClientBeanDefinitions; |
||||||
|
|
||||||
|
public FeignClientBeanFactoryInitializationAotProcessor(GenericApplicationContext context, FeignClientFactory feignClientFactory) { |
||||||
|
this.context = context; |
||||||
|
this.feignClientBeanDefinitions = getFeignClientBeanDefinitions(feignClientFactory); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) { |
||||||
|
return feignClientBeanDefinitions |
||||||
|
.containsKey(registeredBean.getBeanClass().getName()); |
||||||
|
} |
||||||
|
|
||||||
|
private Map<String, BeanDefinition> getFeignClientBeanDefinitions(FeignClientFactory feignClientFactory) { |
||||||
|
Map<String, FeignClientSpecification> configurations = feignClientFactory.getConfigurations(); |
||||||
|
return configurations.values().stream() |
||||||
|
.map(FeignClientSpecification::getClassName) |
||||||
|
.filter(Objects::nonNull) |
||||||
|
.filter(className -> !className.equals("default")) |
||||||
|
.map(className -> Map.entry(className, context.getBeanDefinition(className))) |
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { |
||||||
|
if (!feignClientBeanDefinitions.isEmpty()) { |
||||||
|
return new AotContribution(feignClientBeanDefinitions); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private static class AotContribution implements BeanFactoryInitializationAotContribution { |
||||||
|
|
||||||
|
private final Map<String, BeanDefinition> feignClientBeanDefinitions; |
||||||
|
|
||||||
|
private AotContribution(Map<String, BeanDefinition> feignClientBeanDefinitions) { |
||||||
|
this.feignClientBeanDefinitions = feignClientBeanDefinitions; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) { |
||||||
|
Set<String> feignClientRegistrationMethods = feignClientBeanDefinitions.values() |
||||||
|
.stream() |
||||||
|
.map(beanDefinition -> { |
||||||
|
Assert.notNull(beanDefinition, "beanDefinition cannot be null"); |
||||||
|
Assert.isInstanceOf(GenericBeanDefinition.class, beanDefinition); |
||||||
|
GenericBeanDefinition registeredBeanDefinition = (GenericBeanDefinition) beanDefinition; |
||||||
|
Object factoryBeanObject = registeredBeanDefinition.getAttribute("feignClientsRegistrarFactoryBean"); |
||||||
|
Assert.isInstanceOf(FeignClientFactoryBean.class, factoryBeanObject); |
||||||
|
FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) factoryBeanObject; |
||||||
|
Assert.notNull(factoryBean, "factoryBean cannot be null"); |
||||||
|
return beanFactoryInitializationCode.getMethods() |
||||||
|
.add(buildMethodName(factoryBean.getType().getSimpleName()), |
||||||
|
method -> generateFeignClientRegistrationMethod(method, factoryBean, registeredBeanDefinition)) |
||||||
|
.getName(); |
||||||
|
}).collect(Collectors.toSet()); |
||||||
|
MethodReference initializerMethod = beanFactoryInitializationCode.getMethods() |
||||||
|
.add("initialize", method -> generateInitializerMethod(method, feignClientRegistrationMethods)) |
||||||
|
.toMethodReference(); |
||||||
|
beanFactoryInitializationCode.addInitializer(initializerMethod); |
||||||
|
} |
||||||
|
|
||||||
|
private String buildMethodName(String clientName) { |
||||||
|
return "register" + clientName + "FeignClient"; |
||||||
|
} |
||||||
|
|
||||||
|
private void generateInitializerMethod(MethodSpec.Builder method, Set<String> feignClientRegistrationMethods) { |
||||||
|
method.addModifiers(Modifier.PUBLIC); |
||||||
|
method.addParameter(DefaultListableBeanFactory.class, "registry"); |
||||||
|
feignClientRegistrationMethods.forEach(feignClientRegistrationMethod -> method.addStatement("$N(registry)", feignClientRegistrationMethod)); |
||||||
|
} |
||||||
|
|
||||||
|
private void generateFeignClientRegistrationMethod(MethodSpec.Builder method, FeignClientFactoryBean registeredFactoryBean, GenericBeanDefinition registeredBeanDefinition) { |
||||||
|
String qualifiers = "{\"" + String.join("\",\"", registeredFactoryBean.getQualifiers()) + "\"}"; |
||||||
|
method |
||||||
|
.addJavadoc("register Feign Client: $L", registeredBeanDefinition.getBeanClassName()) |
||||||
|
.addModifiers(Modifier.PUBLIC, Modifier.STATIC) |
||||||
|
.addParameter(BeanDefinitionRegistry.class, "registry") |
||||||
|
.addStatement("Class clazz = $T.resolveClassName(\"$L\", null)", ClassUtils.class, registeredBeanDefinition.getBeanClassName()) |
||||||
|
.addStatement("$T beanFactory = registry instanceof $T ? ($T) registry : null", |
||||||
|
ConfigurableBeanFactory.class, ConfigurableBeanFactory.class, ConfigurableBeanFactory.class) |
||||||
|
.addStatement("$T factoryBean = new $T()", FeignClientFactoryBean.class, FeignClientFactoryBean.class) |
||||||
|
.addStatement("factoryBean.setBeanFactory(beanFactory)") |
||||||
|
.addStatement("factoryBean.setName(\"$L\")", registeredFactoryBean.getName()) |
||||||
|
.addStatement("factoryBean.setContextId(\"$L\")", registeredFactoryBean.getContextId()) |
||||||
|
.addStatement("factoryBean.setType($T.class)", registeredFactoryBean.getType()) |
||||||
|
.addStatement("factoryBean.setUrl($L)", registeredFactoryBean.getUrl()) |
||||||
|
.addStatement("factoryBean.setPath($L)", registeredFactoryBean.getPath()) |
||||||
|
.addStatement("factoryBean.setDismiss404($L)", registeredFactoryBean.isDismiss404()) |
||||||
|
.addStatement("factoryBean.setFallback($T.class)", registeredFactoryBean.getFallback()) |
||||||
|
.addStatement("factoryBean.setFallbackFactory($T.class)", registeredFactoryBean.getFallbackFactory()) |
||||||
|
.addStatement("$T definition = $T.genericBeanDefinition(clazz, () -> factoryBean.getObject())", BeanDefinitionBuilder.class, |
||||||
|
BeanDefinitionBuilder.class) |
||||||
|
.addStatement("definition.setAutowireMode($L)", registeredBeanDefinition.getAutowireMode()) |
||||||
|
.addStatement("definition.setLazyInit($L)", registeredBeanDefinition.getLazyInit()) |
||||||
|
.addStatement("$T beanDefinition = definition.getBeanDefinition()", AbstractBeanDefinition.class) |
||||||
|
.addStatement("beanDefinition.setAttribute(\"$L\", $T.class)", FactoryBean.OBJECT_TYPE_ATTRIBUTE, registeredFactoryBean.getType()) |
||||||
|
.addStatement("beanDefinition.setAttribute(\"feignClientsRegistrarFactoryBean\", factoryBean)") |
||||||
|
.addStatement("beanDefinition.setPrimary($L)", registeredBeanDefinition.isPrimary()) |
||||||
|
.addStatement("$T holder = new $T(beanDefinition, \"$L\", new String[]$L)", BeanDefinitionHolder.class, |
||||||
|
BeanDefinitionHolder.class, registeredBeanDefinition.getBeanClassName(), qualifiers) |
||||||
|
.addStatement("$T.registerBeanDefinition(holder, registry) ", BeanDefinitionReaderUtils.class); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue