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

Fixes gh-894
This commit is contained in:
Pedro Alvarado
2016-04-23 17:28:45 -04:00
committed by Dave Syer
parent 8b306df132
commit 2b40952429
3 changed files with 42 additions and 4 deletions

View File

@@ -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.<AnnotatedParameterProcessor> 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<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) {
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<String> setTemplateParameter(String name,
Collection<String> rest) {
Collection<String> rest) {
return addTemplatedParam(rest, name);
}
}
@@ -331,4 +353,4 @@ public class SpringMvcContract extends Contract.BaseContract {
}
}
}
}

View File

@@ -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<Hello> 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<Hello> getHellos() {
ArrayList<Hello> 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();

View File

@@ -42,4 +42,5 @@ zuul:
url: http://localhost:8081
path: /stores/**
feignClient:
localappName: localapp
localappName: localapp
methodLevelRequestMappingPath: /hello2