Commit 2b409524 by Pedro Alvarado Committed by Dave Syer

Add support for placeholders to Feign spring-mvc RequestMapping annotation.

Fixes gh-894
parent 8b306df1
...@@ -31,12 +31,17 @@ import org.springframework.cloud.netflix.feign.AnnotatedParameterProcessor; ...@@ -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.PathVariableParameterProcessor;
import org.springframework.cloud.netflix.feign.annotation.RequestHeaderParameterProcessor; import org.springframework.cloud.netflix.feign.annotation.RequestHeaderParameterProcessor;
import org.springframework.cloud.netflix.feign.annotation.RequestParamParameterProcessor; 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.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService; 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.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import feign.Contract; import feign.Contract;
...@@ -51,7 +56,7 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.findMerg ...@@ -51,7 +56,7 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.findMerg
/** /**
* @author Spencer Gibb * @author Spencer Gibb
*/ */
public class SpringMvcContract extends Contract.BaseContract { public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
private static final String ACCEPT = "Accept"; private static final String ACCEPT = "Accept";
...@@ -64,6 +69,7 @@ public class SpringMvcContract extends Contract.BaseContract { ...@@ -64,6 +69,7 @@ public class SpringMvcContract extends Contract.BaseContract {
private final ConversionService conversionService; private final ConversionService conversionService;
private final Param.Expander expander; private final Param.Expander expander;
private ResourceLoader resourceLoader = new DefaultResourceLoader();
public SpringMvcContract() { public SpringMvcContract() {
this(Collections.<AnnotatedParameterProcessor> emptyList()); this(Collections.<AnnotatedParameterProcessor> emptyList());
...@@ -95,6 +101,11 @@ public class SpringMvcContract extends Contract.BaseContract { ...@@ -95,6 +101,11 @@ public class SpringMvcContract extends Contract.BaseContract {
} }
@Override @Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) { public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
this.processedMethods.put(Feign.configKey(targetType, method), method); this.processedMethods.put(Feign.configKey(targetType, method), method);
MethodMetadata md = super.parseAndValidateMetadata(targetType, method); MethodMetadata md = super.parseAndValidateMetadata(targetType, method);
...@@ -108,6 +119,7 @@ public class SpringMvcContract extends Contract.BaseContract { ...@@ -108,6 +119,7 @@ public class SpringMvcContract extends Contract.BaseContract {
checkState(pathValue != null, checkState(pathValue != null,
"RequestMapping.value() was empty on type %s", "RequestMapping.value() was empty on type %s",
method.getDeclaringClass().getName()); method.getDeclaringClass().getName());
pathValue = resolve(pathValue);
if (!pathValue.startsWith("/")) { if (!pathValue.startsWith("/")) {
pathValue = "/" + pathValue; pathValue = "/" + pathValue;
} }
...@@ -148,6 +160,7 @@ public class SpringMvcContract extends Contract.BaseContract { ...@@ -148,6 +160,7 @@ public class SpringMvcContract extends Contract.BaseContract {
if (methodMapping.value().length > 0) { if (methodMapping.value().length > 0) {
String pathValue = emptyToNull(methodMapping.value()[0]); String pathValue = emptyToNull(methodMapping.value()[0]);
if (pathValue != null) { if (pathValue != null) {
pathValue = resolve(pathValue);
// Append path from @RequestMapping if value is present on method // Append path from @RequestMapping if value is present on method
if (!pathValue.startsWith("/") if (!pathValue.startsWith("/")
&& !data.template().toString().endsWith("/")) { && !data.template().toString().endsWith("/")) {
...@@ -169,6 +182,15 @@ public class SpringMvcContract extends Contract.BaseContract { ...@@ -169,6 +182,15 @@ public class SpringMvcContract extends Contract.BaseContract {
data.indexToExpander(new LinkedHashMap<Integer, Param.Expander>()); data.indexToExpander(new LinkedHashMap<Integer, Param.Expander>());
} }
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) { private void checkAtMostOne(Method method, Object[] values, String fieldName) {
checkState(values != null && (values.length == 0 || values.length == 1), checkState(values != null && (values.length == 0 || values.length == 1),
"Method %s can only contain at most 1 %s field. Found: %s", "Method %s can only contain at most 1 %s field. Found: %s",
......
...@@ -142,6 +142,9 @@ public class FeignClientTests { ...@@ -142,6 +142,9 @@ public class FeignClientTests {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello(); Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "${feignClient.methodLevelRequestMappingPath}")
Hello getHelloUsingPropertyPlaceHolder();
@RequestMapping(method = RequestMethod.GET, value = "/hello") @RequestMapping(method = RequestMethod.GET, value = "/hello")
Single<Hello> getHelloSingle(); Single<Hello> getHelloSingle();
...@@ -309,6 +312,11 @@ public class FeignClientTests { ...@@ -309,6 +312,11 @@ public class FeignClientTests {
return new Hello(HELLO_WORLD_1); 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") @RequestMapping(method = RequestMethod.GET, value = "/hellos")
public List<Hello> getHellos() { public List<Hello> getHellos() {
ArrayList<Hello> hellos = getHelloList(); ArrayList<Hello> hellos = getHelloList();
...@@ -399,6 +407,13 @@ public class FeignClientTests { ...@@ -399,6 +407,13 @@ public class FeignClientTests {
} }
@Test @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() { public void testSimpleType() {
Hello hello = this.testClient.getHello(); Hello hello = this.testClient.getHello();
assertNotNull("hello was null", hello); assertNotNull("hello was null", hello);
......
...@@ -43,3 +43,4 @@ zuul: ...@@ -43,3 +43,4 @@ zuul:
path: /stores/** path: /stores/**
feignClient: feignClient:
localappName: localapp localappName: localapp
methodLevelRequestMappingPath: /hello2
\ 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