Add support for feign's QueryMap annotation for Object mapping (#79)
* Add QueryMapParameterProcessor * Add license to QueryMapParameterProcessor * Use SpringQueryMap instead of QueryMap. Add test. * Add documentation and license to SpringQueryMap * SpringQueryMap docs * Fix typos
This commit is contained in:
@@ -434,3 +434,37 @@ via discovery, and all requests are executed in a
|
||||
<<hystrix-fallbacks-for-routes, hystrix command>>, so
|
||||
failures will show up in Hystrix metrics, and once the circuit is open
|
||||
the proxy will not try to contact the service.
|
||||
|
||||
=== Feign `@QueryMap` support
|
||||
|
||||
The OpenFeign `@QueryMap` annotation provides support for POJOs to be used as
|
||||
GET parameter maps. Unfortunately, the default OpenFeign QueryMap annotation is
|
||||
incompatible with Spring because it lacks a `value` property.
|
||||
|
||||
Spring Cloud OpenFeign provides an equivalent `@SpringQueryMap` annotation, which
|
||||
is used to annotate a POJO or Map parameter as a query parameter map.
|
||||
|
||||
For example, the `Params` class defines parameters `param1` and `param2`:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
// Params.java
|
||||
public class Params {
|
||||
private String param1;
|
||||
private String param2;
|
||||
|
||||
// [Getters and setters omitted for brevity]
|
||||
}
|
||||
----
|
||||
|
||||
The following feign client uses the `Params` class by using the `@SpringQueryMap` annotation:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@FeignClient("demo")
|
||||
public class DemoTemplate {
|
||||
|
||||
@GetMapping(path = "/demo")
|
||||
String demoEndpoint(@SpringQueryMap Params params);
|
||||
}
|
||||
----
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import feign.QueryMap;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Spring MVC equivalent of OpenFeign's {@link feign.QueryMap} parameter annotation.
|
||||
*
|
||||
* @author Aram Peres
|
||||
* @see feign.QueryMap
|
||||
* @see org.springframework.cloud.openfeign.annotation.QueryMapParameterProcessor
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
public @interface SpringQueryMap {
|
||||
|
||||
/**
|
||||
* Alias for {@link #encoded()}.
|
||||
*
|
||||
* @see QueryMap#encoded()
|
||||
*/
|
||||
@AliasFor("encoded")
|
||||
boolean value() default false;
|
||||
|
||||
/**
|
||||
* Specifies whether parameter names and values are already encoded.
|
||||
*
|
||||
* @see QueryMap#encoded()
|
||||
*/
|
||||
@AliasFor("value")
|
||||
boolean encoded() default false;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign.annotation;
|
||||
|
||||
import feign.MethodMetadata;
|
||||
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
|
||||
import org.springframework.cloud.openfeign.SpringQueryMap;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* {@link SpringQueryMap} parameter processor.
|
||||
*
|
||||
* @author Aram Peres
|
||||
* @see AnnotatedParameterProcessor
|
||||
*/
|
||||
public class QueryMapParameterProcessor implements AnnotatedParameterProcessor {
|
||||
|
||||
private static final Class<SpringQueryMap> ANNOTATION = SpringQueryMap.class;
|
||||
|
||||
@Override
|
||||
public Class<? extends Annotation> getAnnotationType() {
|
||||
return ANNOTATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
|
||||
int paramIndex = context.getParameterIndex();
|
||||
MethodMetadata metadata = context.getMethodMetadata();
|
||||
if (metadata.queryMapIndex() == null) {
|
||||
metadata.queryMapIndex(paramIndex);
|
||||
metadata.queryMapEncoded(SpringQueryMap.class.cast(annotation).encoded());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,6 +31,7 @@ import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
|
||||
import org.springframework.cloud.openfeign.annotation.PathVariableParameterProcessor;
|
||||
import org.springframework.cloud.openfeign.annotation.QueryMapParameterProcessor;
|
||||
import org.springframework.cloud.openfeign.annotation.RequestHeaderParameterProcessor;
|
||||
import org.springframework.cloud.openfeign.annotation.RequestParamParameterProcessor;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
@@ -62,6 +63,7 @@ import feign.Param;
|
||||
* @author Spencer Gibb
|
||||
* @author Abhijit Sarkar
|
||||
* @author Halvdan Hoem Grelland
|
||||
* @author Aram Peres
|
||||
*/
|
||||
public class SpringMvcContract extends Contract.BaseContract
|
||||
implements ResourceLoaderAware {
|
||||
@@ -332,6 +334,7 @@ public class SpringMvcContract extends Contract.BaseContract
|
||||
annotatedArgumentResolvers.add(new PathVariableParameterProcessor());
|
||||
annotatedArgumentResolvers.add(new RequestParamParameterProcessor());
|
||||
annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor());
|
||||
annotatedArgumentResolvers.add(new QueryMapParameterProcessor());
|
||||
|
||||
return annotatedArgumentResolvers;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
* Copyright 2013-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,6 +31,7 @@ import feign.Param;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.cloud.openfeign.SpringQueryMap;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.format.annotation.NumberFormat;
|
||||
@@ -61,6 +62,7 @@ import feign.MethodMetadata;
|
||||
/**
|
||||
* @author chadjaros
|
||||
* @author Halvdan Hoem Grelland
|
||||
* @author Aram Peres
|
||||
*/
|
||||
public class SpringMvcContractTests {
|
||||
private static final Class<?> EXECUTABLE_TYPE;
|
||||
@@ -488,6 +490,20 @@ public class SpringMvcContractTests {
|
||||
assertEquals("{aParam}", params.get("aParam").iterator().next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessQueryMapObject() throws Exception {
|
||||
Method method = TestTemplate_QueryMap.class.getDeclaredMethod("queryMapObject",
|
||||
TestObject.class, String.class);
|
||||
MethodMetadata data = this.contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertEquals("/queryMapObject", data.template().url());
|
||||
assertEquals("GET", data.template().method());
|
||||
assertEquals(0, data.queryMapIndex().intValue());
|
||||
Map<String, Collection<String>> params = data.template().queries();
|
||||
assertEquals("{aParam}", params.get("aParam").iterator().next());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testProcessQueryMapMoreThanOnce() throws Exception {
|
||||
Method method = TestTemplate_QueryMap.class.getDeclaredMethod(
|
||||
@@ -573,6 +589,11 @@ public class SpringMvcContractTests {
|
||||
String queryMapMoreThanOnce(
|
||||
@RequestParam MultiValueMap<String, String> queryMap1,
|
||||
@RequestParam MultiValueMap<String, String> queryMap2);
|
||||
|
||||
@RequestMapping(path = "/queryMapObject")
|
||||
String queryMapObject(
|
||||
@SpringQueryMap TestObject queryMap,
|
||||
@RequestParam(name = "aParam") String aParam);
|
||||
}
|
||||
|
||||
@JsonAutoDetect
|
||||
|
||||
Reference in New Issue
Block a user