Skip to content

Commit 99bbcba

Browse files
王灿velo
authored andcommitted
add BeanQueryMapEncoder (OpenFeign#802)
* changed default query encoder result from POJO field to getter property * changed default query encoder result from POJO field to getter property * reset mistakenly deleted file * Create PropertyQueryMapEncoder and extract QueryMapEncoder.Default to FieldQueryMapEncoder * rename PropertyQueryMapEncoder to BeanQueryMapEncoder and add README * fix README * add comments to QueryMapEncoder and remove deprecation on Default * rename test name * rename package name queryMap to querymap * format code
1 parent 6ad5584 commit 99bbcba

File tree

8 files changed

+429
-78
lines changed

8 files changed

+429
-78
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,18 @@ public class Example {
690690
}
691691
```
692692

693+
When annotating objects with @QueryMap, the default encoder uses reflection to inspect provided objects Fields to expand the objects values into a query string. If you prefer that the query string be built using getter and setter methods, as defined in the Java Beans API, please use the BeanQueryMapEncoder
694+
695+
```java
696+
public class Example {
697+
public static void main(String[] args) {
698+
MyApi myApi = Feign.builder()
699+
.queryMapEncoder(new BeanQueryMapEncoder())
700+
.target(MyApi.class, "https://api.hostname.com");
701+
}
702+
}
703+
```
704+
693705
### Error Handling
694706
If you need more control over handling unexpected responses, Feign instances can
695707
register a custom `ErrorDecoder` via the builder.

core/src/main/java/feign/QueryMapEncoder.java

Lines changed: 13 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@
1313
*/
1414
package feign;
1515

16-
import feign.codec.EncodeException;
17-
import java.lang.reflect.Field;
18-
import java.util.ArrayList;
19-
import java.util.Collections;
20-
import java.util.HashMap;
21-
import java.util.List;
16+
import feign.querymap.FieldQueryMapEncoder;
17+
import feign.querymap.BeanQueryMapEncoder;
2218
import java.util.Map;
2319

2420
/**
2521
* A QueryMapEncoder encodes Objects into maps of query parameter names to values.
22+
*
23+
* @see FieldQueryMapEncoder
24+
* @see BeanQueryMapEncoder
25+
*
2626
*/
2727
public interface QueryMapEncoder {
2828

@@ -34,55 +34,12 @@ public interface QueryMapEncoder {
3434
*/
3535
Map<String, Object> encode(Object object);
3636

37-
class Default implements QueryMapEncoder {
38-
39-
private final Map<Class<?>, ObjectParamMetadata> classToMetadata =
40-
new HashMap<Class<?>, ObjectParamMetadata>();
41-
42-
@Override
43-
public Map<String, Object> encode(Object object) throws EncodeException {
44-
try {
45-
ObjectParamMetadata metadata = getMetadata(object.getClass());
46-
Map<String, Object> fieldNameToValue = new HashMap<String, Object>();
47-
for (Field field : metadata.objectFields) {
48-
Object value = field.get(object);
49-
if (value != null && value != object) {
50-
fieldNameToValue.put(field.getName(), value);
51-
}
52-
}
53-
return fieldNameToValue;
54-
} catch (IllegalAccessException e) {
55-
throw new EncodeException("Failure encoding object into query map", e);
56-
}
57-
}
58-
59-
private ObjectParamMetadata getMetadata(Class<?> objectType) {
60-
ObjectParamMetadata metadata = classToMetadata.get(objectType);
61-
if (metadata == null) {
62-
metadata = ObjectParamMetadata.parseObjectType(objectType);
63-
classToMetadata.put(objectType, metadata);
64-
}
65-
return metadata;
66-
}
67-
68-
private static class ObjectParamMetadata {
69-
70-
private final List<Field> objectFields;
71-
72-
private ObjectParamMetadata(List<Field> objectFields) {
73-
this.objectFields = Collections.unmodifiableList(objectFields);
74-
}
75-
76-
private static ObjectParamMetadata parseObjectType(Class<?> type) {
77-
List<Field> fields = new ArrayList<Field>();
78-
for (Field field : type.getDeclaredFields()) {
79-
if (!field.isAccessible()) {
80-
field.setAccessible(true);
81-
}
82-
fields.add(field);
83-
}
84-
return new ObjectParamMetadata(fields);
85-
}
86-
}
37+
/**
38+
* @deprecated use {@link BeanQueryMapEncoder} instead. default encoder uses reflection to inspect
39+
* provided objects Fields to expand the objects values into a query string. If you
40+
* prefer that the query string be built using getter and setter methods, as defined
41+
* in the Java Beans API, please use the {@link BeanQueryMapEncoder}
42+
*/
43+
class Default extends FieldQueryMapEncoder {
8744
}
8845
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* Copyright 2012-2018 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.querymap;
15+
16+
import feign.QueryMapEncoder;
17+
import feign.codec.EncodeException;
18+
import java.beans.IntrospectionException;
19+
import java.beans.Introspector;
20+
import java.beans.PropertyDescriptor;
21+
import java.lang.reflect.InvocationTargetException;
22+
import java.util.*;
23+
24+
/**
25+
* the query map will be generated using java beans accessible getter property as query parameter
26+
* names.
27+
*
28+
* eg: "/uri?name={name}&number={number}"
29+
*
30+
* order of included query parameters not guaranteed, and as usual, if any value is null, it will be
31+
* left out
32+
*/
33+
public class BeanQueryMapEncoder implements QueryMapEncoder {
34+
private final Map<Class<?>, ObjectParamMetadata> classToMetadata =
35+
new HashMap<Class<?>, ObjectParamMetadata>();
36+
37+
@Override
38+
public Map<String, Object> encode(Object object) throws EncodeException {
39+
try {
40+
ObjectParamMetadata metadata = getMetadata(object.getClass());
41+
Map<String, Object> propertyNameToValue = new HashMap<String, Object>();
42+
for (PropertyDescriptor pd : metadata.objectProperties) {
43+
Object value = pd.getReadMethod().invoke(object);
44+
if (value != null && value != object) {
45+
propertyNameToValue.put(pd.getName(), value);
46+
}
47+
}
48+
return propertyNameToValue;
49+
} catch (IllegalAccessException | IntrospectionException | InvocationTargetException e) {
50+
throw new EncodeException("Failure encoding object into query map", e);
51+
}
52+
}
53+
54+
private ObjectParamMetadata getMetadata(Class<?> objectType) throws IntrospectionException {
55+
ObjectParamMetadata metadata = classToMetadata.get(objectType);
56+
if (metadata == null) {
57+
metadata = ObjectParamMetadata.parseObjectType(objectType);
58+
classToMetadata.put(objectType, metadata);
59+
}
60+
return metadata;
61+
}
62+
63+
private static class ObjectParamMetadata {
64+
65+
private final List<PropertyDescriptor> objectProperties;
66+
67+
private ObjectParamMetadata(List<PropertyDescriptor> objectProperties) {
68+
this.objectProperties = Collections.unmodifiableList(objectProperties);
69+
}
70+
71+
private static ObjectParamMetadata parseObjectType(Class<?> type)
72+
throws IntrospectionException {
73+
List<PropertyDescriptor> properties = new ArrayList<PropertyDescriptor>();
74+
75+
for (PropertyDescriptor pd : Introspector.getBeanInfo(type).getPropertyDescriptors()) {
76+
boolean isGetterMethod = pd.getReadMethod() != null && !"class".equals(pd.getName());
77+
if (isGetterMethod) {
78+
properties.add(pd);
79+
}
80+
}
81+
82+
return new ObjectParamMetadata(properties);
83+
}
84+
}
85+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* Copyright 2012-2018 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.querymap;
15+
16+
import feign.QueryMapEncoder;
17+
import feign.codec.EncodeException;
18+
import java.lang.reflect.Field;
19+
import java.util.*;
20+
21+
/**
22+
* the query map will be generated using member variable names as query parameter names.
23+
*
24+
* eg: "/uri?name={name}&number={number}"
25+
*
26+
* order of included query parameters not guaranteed, and as usual, if any value is null, it will be
27+
* left out
28+
*/
29+
public class FieldQueryMapEncoder implements QueryMapEncoder {
30+
31+
private final Map<Class<?>, ObjectParamMetadata> classToMetadata =
32+
new HashMap<Class<?>, ObjectParamMetadata>();
33+
34+
@Override
35+
public Map<String, Object> encode(Object object) throws EncodeException {
36+
try {
37+
ObjectParamMetadata metadata = getMetadata(object.getClass());
38+
Map<String, Object> fieldNameToValue = new HashMap<String, Object>();
39+
for (Field field : metadata.objectFields) {
40+
Object value = field.get(object);
41+
if (value != null && value != object) {
42+
fieldNameToValue.put(field.getName(), value);
43+
}
44+
}
45+
return fieldNameToValue;
46+
} catch (IllegalAccessException e) {
47+
throw new EncodeException("Failure encoding object into query map", e);
48+
}
49+
}
50+
51+
private ObjectParamMetadata getMetadata(Class<?> objectType) {
52+
ObjectParamMetadata metadata = classToMetadata.get(objectType);
53+
if (metadata == null) {
54+
metadata = ObjectParamMetadata.parseObjectType(objectType);
55+
classToMetadata.put(objectType, metadata);
56+
}
57+
return metadata;
58+
}
59+
60+
private static class ObjectParamMetadata {
61+
62+
private final List<Field> objectFields;
63+
64+
private ObjectParamMetadata(List<Field> objectFields) {
65+
this.objectFields = Collections.unmodifiableList(objectFields);
66+
}
67+
68+
private static ObjectParamMetadata parseObjectType(Class<?> type) {
69+
List<Field> fields = new ArrayList<Field>();
70+
for (Field field : type.getDeclaredFields()) {
71+
if (!field.isAccessible()) {
72+
field.setAccessible(true);
73+
}
74+
fields.add(field);
75+
}
76+
return new ObjectParamMetadata(fields);
77+
}
78+
}
79+
}

core/src/test/java/feign/DefaultQueryMapEncoderTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
*/
1414
package feign;
1515

16-
import java.util.HashMap;
17-
import java.util.Map;
1816
import org.junit.Rule;
1917
import org.junit.Test;
2018
import org.junit.rules.ExpectedException;
19+
import java.util.HashMap;
20+
import java.util.Map;
2121
import static org.junit.Assert.assertEquals;
2222
import static org.junit.Assert.assertTrue;
2323

0 commit comments

Comments
 (0)