diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java index 50d46d844..d02aebed1 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java @@ -38,6 +38,8 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; @@ -58,7 +60,8 @@ * @author Olga Maciaszek-Sharma * @author Ilia Ilinykh */ -public class FeignClientFactoryBean implements FactoryBean, InitializingBean, ApplicationContextAware { +public class FeignClientFactoryBean + implements FactoryBean, InitializingBean, ApplicationContextAware, BeanFactoryAware { /*********************************** * WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some @@ -81,6 +84,8 @@ public class FeignClientFactoryBean implements FactoryBean, Initializing private ApplicationContext applicationContext; + private BeanFactory beanFactory; + private Class fallback = void.class; private Class fallbackFactory = void.class; @@ -125,7 +130,8 @@ private void applyBuildCustomizers(FeignContext context, Feign.Builder builder) } protected void configureFeign(FeignContext context, Feign.Builder builder) { - FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class); + FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class) + : applicationContext.getBean(FeignClientProperties.class); FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class); setInheritParentContext(feignClientConfigurer.inheritParentConfiguration()); @@ -261,7 +267,7 @@ protected void configureUsingProperties(FeignClientProperties.FeignClientConfigu private T getOrInstantiate(Class tClass) { try { - return applicationContext.getBean(tClass); + return beanFactory != null ? beanFactory.getBean(tClass) : applicationContext.getBean(tClass); } catch (NoSuchBeanDefinitionException e) { return BeanUtils.instantiateClass(tClass); @@ -321,7 +327,8 @@ public Object getObject() { * information */ T getTarget() { - FeignContext context = applicationContext.getBean(FeignContext.class); + FeignContext context = this.beanFactory != null ? this.beanFactory.getBean(FeignContext.class) + : applicationContext.getBean(FeignContext.class); Feign.Builder builder = feign(context); if (!StringUtils.hasText(url)) { @@ -437,6 +444,7 @@ public ApplicationContext getApplicationContext() { @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.applicationContext = context; + this.beanFactory = context; } public Class getFallback() { @@ -464,7 +472,8 @@ public boolean equals(Object o) { return false; } FeignClientFactoryBean that = (FeignClientFactoryBean) o; - return Objects.equals(applicationContext, that.applicationContext) && decode404 == that.decode404 + return Objects.equals(applicationContext, that.applicationContext) + && Objects.equals(beanFactory, that.beanFactory) && decode404 == that.decode404 && inheritParentContext == that.inheritParentContext && Objects.equals(fallback, that.fallback) && Objects.equals(fallbackFactory, that.fallbackFactory) && Objects.equals(name, that.name) && Objects.equals(path, that.path) && Objects.equals(type, that.type) && Objects.equals(url, that.url); @@ -486,4 +495,9 @@ public String toString() { .append(fallbackFactory).append("}").toString(); } + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java index 11a545f76..cd9042658 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java @@ -30,6 +30,9 @@ import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.BeanExpressionContext; +import org.springframework.beans.factory.config.BeanExpressionResolver; +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; @@ -194,23 +197,40 @@ public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegi private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map attributes) { String className = annotationMetadata.getClassName(); - BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class); - validate(attributes); - definition.addPropertyValue("url", getUrl(attributes)); - definition.addPropertyValue("path", getPath(attributes)); + Class clazz = ClassUtils.resolveClassName(className, null); + ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory + ? (ConfigurableBeanFactory) registry : null; + String contextId = getContextId(beanFactory, attributes); String name = getName(attributes); - definition.addPropertyValue("name", name); - String contextId = getContextId(attributes); - definition.addPropertyValue("contextId", contextId); - definition.addPropertyValue("type", className); - definition.addPropertyValue("decode404", attributes.get("decode404")); - definition.addPropertyValue("fallback", attributes.get("fallback")); - definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory")); + FeignClientFactoryBean factoryBean = new FeignClientFactoryBean(); + factoryBean.setBeanFactory(beanFactory); + factoryBean.setName(name); + factoryBean.setContextId(contextId); + factoryBean.setType(clazz); + BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> { + factoryBean.setUrl(getUrl(beanFactory, attributes)); + factoryBean.setPath(getPath(beanFactory, attributes)); + factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404")))); + Object fallback = attributes.get("fallback"); + if (fallback != null) { + factoryBean.setFallback(fallback instanceof Class ? (Class) fallback + : ClassUtils.resolveClassName(fallback.toString(), null)); + } + Object fallbackFactory = attributes.get("fallbackFactory"); + if (fallbackFactory != null) { + factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class) fallbackFactory + : ClassUtils.resolveClassName(fallbackFactory.toString(), null)); + } + return factoryBean.getObject(); + }); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); + definition.setLazyInit(true); + validate(attributes); String alias = contextId + "FeignClient"; AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className); + beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean); // has a default, won't be null boolean primary = (Boolean) attributes.get("primary"); @@ -235,6 +255,10 @@ private void validate(Map attributes) { } /* for testing */ String getName(Map attributes) { + return getName(null, attributes); + } + + String getName(ConfigurableBeanFactory beanFactory, Map attributes) { String name = (String) attributes.get("serviceId"); if (!StringUtils.hasText(name)) { name = (String) attributes.get("name"); @@ -242,34 +266,42 @@ private void validate(Map attributes) { if (!StringUtils.hasText(name)) { name = (String) attributes.get("value"); } - name = resolve(name); + name = resolve(beanFactory, name); return getName(name); } - private String getContextId(Map attributes) { + private String getContextId(ConfigurableBeanFactory beanFactory, Map attributes) { String contextId = (String) attributes.get("contextId"); if (!StringUtils.hasText(contextId)) { return getName(attributes); } - contextId = resolve(contextId); + contextId = resolve(beanFactory, contextId); return getName(contextId); } - private String resolve(String value) { + private String resolve(ConfigurableBeanFactory beanFactory, String value) { if (StringUtils.hasText(value)) { - return this.environment.resolvePlaceholders(value); + if (beanFactory == null) { + return this.environment.resolvePlaceholders(value); + } + BeanExpressionResolver resolver = beanFactory.getBeanExpressionResolver(); + String resolved = beanFactory.resolveEmbeddedValue(value); + if (resolver == null) { + return resolved; + } + return String.valueOf(resolver.evaluate(resolved, new BeanExpressionContext(beanFactory, null))); } return value; } - private String getUrl(Map attributes) { - String url = resolve((String) attributes.get("url")); + private String getUrl(ConfigurableBeanFactory beanFactory, Map attributes) { + String url = resolve(beanFactory, (String) attributes.get("url")); return getUrl(url); } - private String getPath(Map attributes) { - String path = resolve((String) attributes.get("path")); + private String getPath(ConfigurableBeanFactory beanFactory, Map attributes) { + String path = resolve(beanFactory, (String) attributes.get("path")); return getPath(path); } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingConfigurerTest.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingConfigurerTest.java index 060ce1204..e342a1b5a 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingConfigurerTest.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingConfigurerTest.java @@ -26,10 +26,10 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; @@ -49,18 +49,19 @@ "feign.client.config.default.requestInterceptors[1]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.BarRequestInterceptor" }) public class FeignClientUsingConfigurerTest { - private static final String BEAN_NAME_PREFIX = "&org.springframework.cloud.openfeign.FeignClientUsingConfigurerTest$"; + private static final String BEAN_NAME_PREFIX = "org.springframework.cloud.openfeign.FeignClientUsingConfigurerTest$"; @Autowired - private ApplicationContext applicationContext; + private ConfigurableListableBeanFactory beanFactory; @Autowired private FeignContext context; @Test public void testFeignClient() { - FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) applicationContext - .getBean(BEAN_NAME_PREFIX + "TestFeignClient"); + FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) beanFactory + .getBeanDefinition(BEAN_NAME_PREFIX + "TestFeignClient") + .getAttribute("feignClientsRegistrarFactoryBean"); Feign.Builder builder = factoryBean.feign(context); List interceptors = (List) getBuilderValue(builder, "requestInterceptors"); @@ -77,8 +78,9 @@ private Object getBuilderValue(Feign.Builder builder, String member) { @Test public void testNoInheritFeignClient() { - FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) applicationContext - .getBean(BEAN_NAME_PREFIX + "NoInheritFeignClient"); + FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) beanFactory + .getBeanDefinition(BEAN_NAME_PREFIX + "NoInheritFeignClient") + .getAttribute("feignClientsRegistrarFactoryBean"); Feign.Builder builder = factoryBean.feign(context); List interceptors = (List) getBuilderValue(builder, "requestInterceptors"); @@ -89,8 +91,9 @@ public void testNoInheritFeignClient() { @Test public void testNoInheritFeignClient_ignoreProperties() { - FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) applicationContext - .getBean(BEAN_NAME_PREFIX + "NoInheritFeignClient"); + FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) beanFactory + .getBeanDefinition(BEAN_NAME_PREFIX + "NoInheritFeignClient") + .getAttribute("feignClientsRegistrarFactoryBean"); Feign.Builder builder = factoryBean.feign(context); assertThat(getBuilderValue(builder, "logLevel")).as("log level not set").isEqualTo(Logger.Level.HEADERS);