From 94d4bdfcc38cb9cc505fe249a0f79f4431b03f8d Mon Sep 17 00:00:00 2001 From: Oreste Luci Date: Thu, 19 Nov 2015 13:23:30 +0100 Subject: [PATCH] Add support for placeholders in @FeignClient definitions Includes name and url attributes. fixes gh-414 --- .../main/asciidoc/spring-cloud-netflix.adoc | 10 ++++++++ .../cloud/netflix/feign/FeignClient.java | 6 +++-- .../netflix/feign/FeignClientsRegistrar.java | 24 ++++++++++++++++++- .../valid/FeignClientValidationTests.java | 22 +++++++++++++++++ .../scanning/FeignClientScanningTests.java | 16 +++++++++++++ .../src/test/resources/application.yml | 2 ++ 6 files changed, 77 insertions(+), 3 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 521599bc..c8640fc1 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -790,6 +790,16 @@ NOTE: The `serviceId` attribute is now deprecated in favor of the `name` attribu WARNING: Previously, using the `url` attribute, did not require the `name` attribute. Using `name` is now required. +Placeholders are supported in the `name` and `url` attributes. + +[source,java,indent=0] +---- +@FeignClient(name = "${feign.name}", url = "${feign.url}") +public interface StoreClient { + //.. +} +---- + Spring Cloud Netflix provides the following beans by default for feign (`BeanType` beanName: `ClassName`): * `Decoder` feignDecoder: `ResponseEntityDecoder` (which wraps a `SpringDecoder`) diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClient.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClient.java index d29f0a50..234072ad 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClient.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClient.java @@ -39,14 +39,16 @@ public @interface FeignClient { /** * The serviceId with optional protocol prefix. Synonym for {@link #serviceId() - * serviceId}. Either serviceId or url must be specified but not both. + * serviceId}. Either serviceId or url must be specified but not both. Can be + * specified as property key, eg: ${propertyKey}. */ @AliasFor("name") String value() default ""; /** * The serviceId with optional protocol prefix. Synonym for {@link #value() value}. - * Either serviceId or url must be specified but not both. + * Either serviceId or url must be specified but not both. Can be + * specified as property key, eg: ${propertyKey}. * * @deprecated use {@link #name() name} instead */ diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientsRegistrar.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientsRegistrar.java index 61365b31..b46ec3c9 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientsRegistrar.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/FeignClientsRegistrar.java @@ -18,8 +18,10 @@ package org.springframework.cloud.netflix.feign; import java.io.IOException; import java.lang.annotation.Annotation; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -34,6 +36,7 @@ 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.context.ConfigurableApplicationContext; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; @@ -186,9 +189,11 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, if (!StringUtils.hasText(name)) { name = (String) attributes.get("value"); } + name = resolve(name); if (!StringUtils.hasText(name)) { return ""; } + String host = null; try { host = new URI("http://" + name).getHost(); @@ -199,8 +204,25 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, return name; } + private String resolve(String value) { + if (StringUtils.hasText(value ) + && this.resourceLoader instanceof ConfigurableApplicationContext) { + return ((ConfigurableApplicationContext)this.resourceLoader) + .getEnvironment().resolvePlaceholders(value); + } + return value; + } + private String getUrl(Map attributes) { - return (String) attributes.get("url"); + String url = resolve((String) attributes.get("url")); + if (StringUtils.hasText(url)) { + try { + new URL(url); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(url + " is malformed", e); + } + } + return url; } protected ClassPathScanningCandidateComponentProvider getScanner() { diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientValidationTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientValidationTests.java index d0487d05..f255c3a8 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientValidationTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientValidationTests.java @@ -57,6 +57,28 @@ public class FeignClientValidationTests { } + @Test + public void validPlaceholder() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + PlaceholderUrlConfiguration.class); + assertNotNull(context.getBean(PlaceholderUrlConfiguration.Client.class)); + context.close(); + } + + @Configuration + @Import(FeignAutoConfiguration.class) + @EnableFeignClients(clients = PlaceholderUrlConfiguration.Client.class) + protected static class PlaceholderUrlConfiguration { + + @FeignClient(name="example", url="${feignClient.url:http://example.com}") + interface Client { + @RequestMapping(method = RequestMethod.GET, value = "/") + @Deprecated + String get(); + } + + } + @Test public void validLoadBalanced() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/scanning/FeignClientScanningTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/scanning/FeignClientScanningTests.java index d516b15e..945f0e0a 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/scanning/FeignClientScanningTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/scanning/FeignClientScanningTests.java @@ -60,6 +60,9 @@ public class FeignClientScanningTests { @Autowired private TestClient testClient; + @Autowired + private TestClientByKey testClientByKey; + @Autowired private Client feignClient; @@ -69,6 +72,12 @@ public class FeignClientScanningTests { String getHello(); } + @FeignClient("${feignClient.localappName}") + protected interface TestClientByKey { + @RequestMapping(method = RequestMethod.GET, value = "/hello") + String getHello(); + } + @Configuration @EnableAutoConfiguration @RestController @@ -94,6 +103,13 @@ public class FeignClientScanningTests { assertEquals("first hello didn't match", "hello world 1", hello); } + @Test + public void testSimpleTypeByKey() { + String hello = this.testClientByKey.getHello(); + assertNotNull("hello was null", hello); + assertEquals("first hello didn't match", "hello world 1", hello); + } + // Load balancer with fixed server list for "local" pointing to localhost @Configuration public static class LocalRibbonClientConfiguration { diff --git a/spring-cloud-netflix-core/src/test/resources/application.yml b/spring-cloud-netflix-core/src/test/resources/application.yml index e4c05322..1bd12338 100644 --- a/spring-cloud-netflix-core/src/test/resources/application.yml +++ b/spring-cloud-netflix-core/src/test/resources/application.yml @@ -37,3 +37,5 @@ zuul: stores: url: http://localhost:8081 path: /stores/** +feignClient: + localappName: localapp \ No newline at end of file