Commit 94d4bdfc by Oreste Luci Committed by Spencer Gibb

Add support for placeholders in @FeignClient definitions

Includes name and url attributes. fixes gh-414
parent 4721bee8
...@@ -790,6 +790,16 @@ NOTE: The `serviceId` attribute is now deprecated in favor of the `name` attribu ...@@ -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. 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`): Spring Cloud Netflix provides the following beans by default for feign (`BeanType` beanName: `ClassName`):
* `Decoder` feignDecoder: `ResponseEntityDecoder` (which wraps a `SpringDecoder`) * `Decoder` feignDecoder: `ResponseEntityDecoder` (which wraps a `SpringDecoder`)
......
...@@ -39,14 +39,16 @@ public @interface FeignClient { ...@@ -39,14 +39,16 @@ public @interface FeignClient {
/** /**
* The serviceId with optional protocol prefix. Synonym for {@link #serviceId() * 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") @AliasFor("name")
String value() default ""; String value() default "";
/** /**
* The serviceId with optional protocol prefix. Synonym for {@link #value() value}. * 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 * @deprecated use {@link #name() name} instead
*/ */
......
...@@ -18,8 +18,10 @@ package org.springframework.cloud.netflix.feign; ...@@ -18,8 +18,10 @@ package org.springframework.cloud.netflix.feign;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
...@@ -34,6 +36,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; ...@@ -34,6 +36,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.ResourceLoaderAware; import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
...@@ -186,9 +189,11 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ...@@ -186,9 +189,11 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
if (!StringUtils.hasText(name)) { if (!StringUtils.hasText(name)) {
name = (String) attributes.get("value"); name = (String) attributes.get("value");
} }
name = resolve(name);
if (!StringUtils.hasText(name)) { if (!StringUtils.hasText(name)) {
return ""; return "";
} }
String host = null; String host = null;
try { try {
host = new URI("http://" + name).getHost(); host = new URI("http://" + name).getHost();
...@@ -199,8 +204,25 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ...@@ -199,8 +204,25 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
return name; 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<String, Object> attributes) { private String getUrl(Map<String, Object> 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() { protected ClassPathScanningCandidateComponentProvider getScanner() {
......
...@@ -58,6 +58,28 @@ public class FeignClientValidationTests { ...@@ -58,6 +58,28 @@ public class FeignClientValidationTests {
} }
@Test @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() { public void validLoadBalanced() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
RibbonAutoConfiguration.class, RibbonAutoConfiguration.class,
......
...@@ -61,6 +61,9 @@ public class FeignClientScanningTests { ...@@ -61,6 +61,9 @@ public class FeignClientScanningTests {
private TestClient testClient; private TestClient testClient;
@Autowired @Autowired
private TestClientByKey testClientByKey;
@Autowired
private Client feignClient; private Client feignClient;
@FeignClient("localapp") @FeignClient("localapp")
...@@ -69,6 +72,12 @@ public class FeignClientScanningTests { ...@@ -69,6 +72,12 @@ public class FeignClientScanningTests {
String getHello(); String getHello();
} }
@FeignClient("${feignClient.localappName}")
protected interface TestClientByKey {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
String getHello();
}
@Configuration @Configuration
@EnableAutoConfiguration @EnableAutoConfiguration
@RestController @RestController
...@@ -94,6 +103,13 @@ public class FeignClientScanningTests { ...@@ -94,6 +103,13 @@ public class FeignClientScanningTests {
assertEquals("first hello didn't match", "hello world 1", hello); 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 // Load balancer with fixed server list for "local" pointing to localhost
@Configuration @Configuration
public static class LocalRibbonClientConfiguration { public static class LocalRibbonClientConfiguration {
......
...@@ -37,3 +37,5 @@ zuul: ...@@ -37,3 +37,5 @@ zuul:
stores: stores:
url: http://localhost:8081 url: http://localhost:8081
path: /stores/** path: /stores/**
feignClient:
localappName: localapp
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment