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 7d729072..cff06615 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 feign.codec.ErrorDecoder; 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,9 +60,10 @@ import org.springframework.util.StringUtils; * @author Matt King * @author Olga Maciaszek-Sharma * @author Ilia Ilinykh + * @author Marcin Grzejszczak */ -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 @@ -83,6 +86,8 @@ class FeignClientFactoryBean private ApplicationContext applicationContext; + private BeanFactory beanFactory; + private Class fallback = void.class; private Class fallbackFactory = void.class; @@ -129,8 +134,9 @@ class FeignClientFactoryBean } 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); @@ -283,7 +289,8 @@ class FeignClientFactoryBean 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); @@ -336,7 +343,7 @@ class FeignClientFactoryBean } @Override - public Object getObject() throws Exception { + public Object getObject() { return getTarget(); } @@ -346,7 +353,9 @@ class FeignClientFactoryBean * information */ T getTarget() { - FeignContext context = applicationContext.getBean(FeignContext.class); + FeignContext context = beanFactory != null + ? beanFactory.getBean(FeignContext.class) + : applicationContext.getBean(FeignContext.class); Feign.Builder builder = feign(context); if (!StringUtils.hasText(url)) { @@ -468,7 +477,8 @@ class FeignClientFactoryBean @Override public void setApplicationContext(ApplicationContext context) throws BeansException { - this.applicationContext = context; + applicationContext = context; + beanFactory = context; } public Class getFallback() { @@ -497,6 +507,7 @@ class FeignClientFactoryBean } FeignClientFactoryBean that = (FeignClientFactoryBean) o; return Objects.equals(applicationContext, that.applicationContext) + && Objects.equals(beanFactory, that.beanFactory) && decode404 == that.decode404 && inheritParentContext == that.inheritParentContext && Objects.equals(fallback, that.fallback) @@ -507,8 +518,8 @@ class FeignClientFactoryBean @Override public int hashCode() { - return Objects.hash(applicationContext, decode404, inheritParentContext, fallback, - fallbackFactory, name, path, type, url); + return Objects.hash(applicationContext, beanFactory, decode404, + inheritParentContext, fallback, fallbackFactory, name, path, type, url); } @Override @@ -519,9 +530,15 @@ class FeignClientFactoryBean .append("decode404=").append(decode404).append(", ") .append("inheritParentContext=").append(inheritParentContext).append(", ") .append("applicationContext=").append(applicationContext).append(", ") + .append("beanFactory=").append(beanFactory).append(", ") .append("fallback=").append(fallback).append(", ") .append("fallbackFactory=").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 1b09c59e..fc88d077 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.AnnotatedBeanDefinition; 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; @@ -53,6 +56,7 @@ import org.springframework.util.StringUtils; * @author Venil Noronha * @author Gang Li * @author Michal Domagala + * @author Marcin Grzejszczak */ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { @@ -204,24 +208,45 @@ class FeignClientsRegistrar 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"); @@ -247,6 +272,10 @@ class FeignClientsRegistrar } /* 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"); @@ -254,34 +283,46 @@ class FeignClientsRegistrar 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 5b82ddf0..34ed5cfd 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.Test; 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 @@ import static org.assertj.core.api.Assertions.assertThat; "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, @@ -79,8 +80,9 @@ public class FeignClientUsingConfigurerTest { @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, @@ -93,8 +95,9 @@ public class FeignClientUsingConfigurerTest { @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")