diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java index 53e05b5c..e796b4ad 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java @@ -56,12 +56,12 @@ import org.springframework.cloud.client.actuator.HasFeatures; import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; import org.springframework.cloud.openfeign.aot.FeignChildContextInitializer; +import org.springframework.cloud.openfeign.aot.FeignClientBeanFactoryInitializationAotProcessor; import org.springframework.cloud.openfeign.security.OAuth2AccessTokenInterceptor; import org.springframework.cloud.openfeign.support.FeignEncoderProperties; import org.springframework.cloud.openfeign.support.FeignHttpClientProperties; import org.springframework.cloud.openfeign.support.PageJacksonModule; import org.springframework.cloud.openfeign.support.SortJacksonModule; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -117,6 +117,12 @@ public class FeignAutoConfiguration { return new FeignChildContextInitializer(parentContext, feignClientFactory); } + @Bean + static FeignClientBeanFactoryInitializationAotProcessor feignClientBeanFactoryInitializationCodeGenerator(GenericApplicationContext applicationContext, + FeignClientFactory feignClientFactory) { + return new FeignClientBeanFactoryInitializationAotProcessor(applicationContext, feignClientFactory); + } + @Bean @ConditionalOnProperty(value = "spring.cloud.openfeign.cache.enabled", matchIfMissing = true) @ConditionalOnBean(CacheInterceptor.class) diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignChildContextInitializer.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignChildContextInitializer.java index 3ab4845e..7eb23c2d 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignChildContextInitializer.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignChildContextInitializer.java @@ -4,7 +4,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -13,33 +12,21 @@ import javax.lang.model.element.Modifier; import org.springframework.aot.generate.GeneratedMethod; import org.springframework.aot.generate.GenerationContext; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; import org.springframework.beans.factory.aot.BeanRegistrationCode; -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.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.GenericBeanDefinition; import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.boot.web.context.WebServerInitializedEvent; import org.springframework.cloud.openfeign.FeignClientFactory; -import org.springframework.cloud.openfeign.FeignClientFactoryBean; import org.springframework.cloud.openfeign.FeignClientSpecification; +import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.aot.ApplicationContextAotGenerator; import org.springframework.context.support.GenericApplicationContext; import org.springframework.javapoet.ClassName; -import org.springframework.javapoet.MethodSpec; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; /** * A {@link BeanRegistrationAotProcessor} that creates an {@link BeanRegistrationAotContribution} for @@ -48,32 +35,28 @@ import org.springframework.util.ClassUtils; * @author Olga Maciaszek-Sharma * @since 4.0.0 */ -public class FeignChildContextInitializer implements BeanRegistrationAotProcessor, ApplicationListener, - BeanRegistrationExcludeFilter { +public class FeignChildContextInitializer implements BeanRegistrationAotProcessor, ApplicationListener { - private final GenericApplicationContext context; + private final ApplicationContext applicationContext; private final FeignClientFactory feignClientFactory; private final Map> applicationContextInitializers; - private final Map feignClientBeanDefinitions; - - public FeignChildContextInitializer(GenericApplicationContext context, FeignClientFactory feignClientFactory) { - this(context, feignClientFactory, new HashMap<>()); + public FeignChildContextInitializer(ApplicationContext applicationContext, FeignClientFactory feignClientFactory) { + this(applicationContext, feignClientFactory, new HashMap<>()); } - public FeignChildContextInitializer(GenericApplicationContext context, FeignClientFactory feignClientFactory, Map> applicationContextInitializers) { - this.context = context; + public FeignChildContextInitializer(ApplicationContext applicationContext, FeignClientFactory feignClientFactory, Map> applicationContextInitializers) { + this.applicationContext = applicationContext; this.feignClientFactory = feignClientFactory; this.applicationContextInitializers = applicationContextInitializers; - feignClientBeanDefinitions = getFeignClientClassNames(); } @Override public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { - Assert.isInstanceOf(ConfigurableApplicationContext.class, context); - ConfigurableApplicationContext context = this.context; + Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext); + ConfigurableApplicationContext context = ((ConfigurableApplicationContext) applicationContext); BeanFactory applicationBeanFactory = context.getBeanFactory(); if (!(registeredBean.getBeanClass().equals(getClass()) && registeredBean.getBeanFactory().equals(applicationBeanFactory))) { @@ -83,7 +66,7 @@ public class FeignChildContextInitializer implements BeanRegistrationAotProcesso Map childContextAotContributions = contextIds.stream() .map(contextId -> Map.entry(contextId, buildChildContext(contextId))) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - return new AotContribution(childContextAotContributions, feignClientBeanDefinitions); + return new AotContribution(childContextAotContributions); } private GenericApplicationContext buildChildContext(String contextId) { @@ -106,13 +89,13 @@ public class FeignChildContextInitializer implements BeanRegistrationAotProcesso .forEach(contextId -> convertedInitializers.put(contextId, (ApplicationContextInitializer) applicationContextInitializers .get(contextId))); - return new FeignChildContextInitializer(context, feignClientFactory, + return new FeignChildContextInitializer(applicationContext, feignClientFactory, convertedInitializers); } @Override public void onApplicationEvent(WebServerInitializedEvent event) { - if (context.equals(event.getApplicationContext())) { + if (applicationContext.equals(event.getApplicationContext())) { applicationContextInitializers.keySet().forEach(contextId -> { GenericApplicationContext childContext = feignClientFactory.buildContext(contextId); applicationContextInitializers.get(contextId).initialize(childContext); @@ -127,33 +110,15 @@ public class FeignChildContextInitializer implements BeanRegistrationAotProcesso return false; } - @Override - public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) { - return feignClientBeanDefinitions - .containsKey(registeredBean.getBeanClass().getName()); - } - - private Map getFeignClientClassNames() { - Map 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)); - } - private static class AotContribution implements BeanRegistrationAotContribution { private final Map childContexts; - private final Map feignClientBeanDefinitions; - public AotContribution(Map childContexts, Map feignClientBeanDefinitions) { + public AotContribution(Map childContexts) { this.childContexts = childContexts.entrySet().stream() .filter(entry -> entry.getValue() != null) .map(entry -> Map.entry(entry.getKey(), entry.getValue())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - this.feignClientBeanDefinitions = feignClientBeanDefinitions; } @Override @@ -184,68 +149,6 @@ public class FeignChildContextInitializer implements BeanRegistrationAotProcesso method.addStatement("return instance.withApplicationContextInitializers(initializers)"); }); beanRegistrationCode.addInstancePostProcessor(postProcessorMethod.toMethodReference()); - - // TODO: ensure the methods are called - feignClientBeanDefinitions.values().forEach(beanDefinition -> { - // TODO: handle problem retrieving - 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"); - generationContext.getGeneratedClasses() - // FIXME: correct generation context - .getOrAddForFeatureComponent(registeredBeanDefinition.getBeanClassName(), generatedInitializerClassNames.get(factoryBean.getContextId()), type -> { - type.addMethod(buildMethodSpec(factoryBean, registeredBeanDefinition)); - } - ); - }); - } - - - // TODO: verify all factory bean method values from registrar! - private MethodSpec buildMethodSpec(FeignClientFactoryBean registeredFactoryBean, GenericBeanDefinition registeredBeanDefinition) { - String qualifiers = "{\"" + String.join("\",\"", registeredFactoryBean.getQualifiers()) + "\"}"; - return MethodSpec.methodBuilder("feignClientRegistration") - .addJavadoc("registerFeignClient") - .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("String[] qualifiers = new String[]{}") - .addStatement("$T holder = new $T(beanDefinition, \"$L\", new String[]$L)", BeanDefinitionHolder.class, - BeanDefinitionHolder.class, registeredBeanDefinition.getBeanClassName(), qualifiers) - .addStatement("$T.registerBeanDefinition(holder, registry) ", BeanDefinitionReaderUtils.class) - .build(); - // TODO -// .addStatement("Class beanType = $T.class", Class.forName(feignClientBeanDefinition.getBeanClassName())) -// .addStatement("$T beanDefinition = new $T(beanType)", RootBeanDefinition.class, RootBeanDefinition.class) -// .addStatement("beanDefinition.setLazyInit($L)", feignClientBeanDefinition.isLazyInit()) -// .addStatement("beanDefinition.setInstanceSupplier(($T) registeredBean -> new $T())", InstanceSupplier.class, FeignClientFactoryBean.class) -// .addStatement("return beanDefinition") - - } } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignClientBeanFactoryInitializationAotProcessor.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignClientBeanFactoryInitializationAotProcessor.java new file mode 100644 index 00000000..d9d6adde --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/aot/FeignClientBeanFactoryInitializationAotProcessor.java @@ -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 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 getFeignClientBeanDefinitions(FeignClientFactory feignClientFactory) { + Map 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 feignClientBeanDefinitions; + + private AotContribution(Map feignClientBeanDefinitions) { + this.feignClientBeanDefinitions = feignClientBeanDefinitions; + } + + @Override + public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) { + Set 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 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); + } + } + +}