From 2b40952429cd3b8ee32605781c0f8fe471a51302 Mon Sep 17 00:00:00 2001 From: Pedro Alvarado Date: Sat, 23 Apr 2016 17:28:45 -0400 Subject: [PATCH] Add support for placeholders to Feign spring-mvc RequestMapping annotation. Fixes gh-894 --- .../feign/support/SpringMvcContract.java | 28 +++++++++++++++++-- .../netflix/feign/valid/FeignClientTests.java | 15 ++++++++++ .../src/test/resources/application.yml | 3 +- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/support/SpringMvcContract.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/support/SpringMvcContract.java index c67b3ee5..fe5029a9 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/support/SpringMvcContract.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/support/SpringMvcContract.java @@ -31,12 +31,17 @@ import org.springframework.cloud.netflix.feign.AnnotatedParameterProcessor; import org.springframework.cloud.netflix.feign.annotation.PathVariableParameterProcessor; import org.springframework.cloud.netflix.feign.annotation.RequestHeaderParameterProcessor; import org.springframework.cloud.netflix.feign.annotation.RequestParamParameterProcessor; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.ResourceLoaderAware; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import feign.Contract; @@ -51,7 +56,7 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.findMerg /** * @author Spencer Gibb */ -public class SpringMvcContract extends Contract.BaseContract { +public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware { private static final String ACCEPT = "Accept"; @@ -64,6 +69,7 @@ public class SpringMvcContract extends Contract.BaseContract { private final ConversionService conversionService; private final Param.Expander expander; + private ResourceLoader resourceLoader = new DefaultResourceLoader(); public SpringMvcContract() { this(Collections. emptyList()); @@ -94,6 +100,11 @@ public class SpringMvcContract extends Contract.BaseContract { this.expander = new ConvertingExpander(conversionService); } + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + @Override public MethodMetadata parseAndValidateMetadata(Class targetType, Method method) { this.processedMethods.put(Feign.configKey(targetType, method), method); @@ -108,6 +119,7 @@ public class SpringMvcContract extends Contract.BaseContract { checkState(pathValue != null, "RequestMapping.value() was empty on type %s", method.getDeclaringClass().getName()); + pathValue = resolve(pathValue); if (!pathValue.startsWith("/")) { pathValue = "/" + pathValue; } @@ -148,6 +160,7 @@ public class SpringMvcContract extends Contract.BaseContract { if (methodMapping.value().length > 0) { String pathValue = emptyToNull(methodMapping.value()[0]); if (pathValue != null) { + pathValue = resolve(pathValue); // Append path from @RequestMapping if value is present on method if (!pathValue.startsWith("/") && !data.template().toString().endsWith("/")) { @@ -169,6 +182,15 @@ public class SpringMvcContract extends Contract.BaseContract { data.indexToExpander(new LinkedHashMap()); } + private String resolve(String value) { + if (StringUtils.hasText(value) + && this.resourceLoader instanceof ConfigurableApplicationContext) { + return ((ConfigurableApplicationContext) this.resourceLoader).getEnvironment() + .resolvePlaceholders(value); + } + return value; + } + private void checkAtMostOne(Method method, Object[] values, String fieldName) { checkState(values != null && (values.length == 0 || values.length == 1), "Method %s can only contain at most 1 %s field. Found: %s", @@ -312,7 +334,7 @@ public class SpringMvcContract extends Contract.BaseContract { @Override public Collection setTemplateParameter(String name, - Collection rest) { + Collection rest) { return addTemplatedParam(rest, name); } } @@ -331,4 +353,4 @@ public class SpringMvcContract extends Contract.BaseContract { } } -} +} \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientTests.java index a7b3c280..5c7e2e6c 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/valid/FeignClientTests.java @@ -142,6 +142,9 @@ public class FeignClientTests { @RequestMapping(method = RequestMethod.GET, value = "/hello") Hello getHello(); + @RequestMapping(method = RequestMethod.GET, value = "${feignClient.methodLevelRequestMappingPath}") + Hello getHelloUsingPropertyPlaceHolder(); + @RequestMapping(method = RequestMethod.GET, value = "/hello") Single getHelloSingle(); @@ -309,6 +312,11 @@ public class FeignClientTests { return new Hello(HELLO_WORLD_1); } + @RequestMapping(method = RequestMethod.GET, value = "/hello2") + public Hello getHello2() { + return new Hello(OI_TERRA_2); + } + @RequestMapping(method = RequestMethod.GET, value = "/hellos") public List getHellos() { ArrayList hellos = getHelloList(); @@ -398,6 +406,13 @@ public class FeignClientTests { assertNotNull("invocationHandler was null", invocationHandler); } + @Test + public void testRequestMappingClassLevelPropertyReplacement() { + Hello hello = this.testClient.getHelloUsingPropertyPlaceHolder(); + assertNotNull("hello was null", hello); + assertEquals("first hello didn't match", new Hello(OI_TERRA_2), hello); + } + @Test public void testSimpleType() { Hello hello = this.testClient.getHello(); diff --git a/spring-cloud-netflix-core/src/test/resources/application.yml b/spring-cloud-netflix-core/src/test/resources/application.yml index 08671407..137a6225 100644 --- a/spring-cloud-netflix-core/src/test/resources/application.yml +++ b/spring-cloud-netflix-core/src/test/resources/application.yml @@ -42,4 +42,5 @@ zuul: url: http://localhost:8081 path: /stores/** feignClient: - localappName: localapp \ No newline at end of file + localappName: localapp + methodLevelRequestMappingPath: /hello2 \ No newline at end of file