【问题标题】:Spring can't construct query params from complex object for rest templateSpring无法从复杂对象构造查询参数以用于REST模板
【发布时间】:2019-04-02 23:31:35
【问题描述】:

问题与Spring's RestTemplate: complex object to query params有些不同 假设有一个请求以对象的形式接受一些参数:

@GetMapping("getRoute")
public ActionResult<Response> get(@Validated GetRequest req) {
//do some logic
}

请求本身如下所示:

public class GetRequest{
    private String firstField;
    private Long secondField;
    private ComplexObject thirdField;

    private static class ComplexObject{
        private String subFirstField;
        private Long subSecondField;
    }
}

因此,当我从 RestTemplate 使用这个对象执行查询时,我想得到一个这样的 URI:

/getRoute?firstField=val&secondField=val&thirdField.subFirstField=val&thirdField.subSecondField=val

我该怎么做?对象绝对可以是任何东西。 更大的问题是如何将这样的 this 对象转换为 UriComponentsBuilder 的 MultiValueMap。

如果是 POST 请求,解决方案会很简单,但我需要它用于 GET。

我只知道Springfox库在生成Swagger API的时候就是用这种方式的,但是里面的逻辑太复杂了。 我的场景:

@SuppressWarnings("unchecked")
public <T> ActionResult<T> getAction(String relativeUrl, Class<T> responseType, @Nullable Object paramsObject) {
    UriComponentsBuilder builder = createUriComponentsBuilder(relativeUrl, paramsObject);
    final ApiErrorCode errorCode;
    try {
        ResponseEntity<ActionResult> responseEntity = this.restTemplate.getForEntity(builder.build().toUri(), ActionResult.class);
        ActionResult responseBody = responseEntity.getBody();

        if (!Objects.requireNonNull(responseBody).isSuccess()) {
            return responseBody;
        }
        return ActionResult.ok(this.mapper.convertValue(responseBody.getValue(), responseType));
    } catch (RestClientException | HttpMessageNotReadableException e) {
        errorCode = ApiErrorCode.API_CONNECTION_ERROR;
    } catch (Exception e) {
        errorCode = ApiErrorCode.INTERNAL_SERVER_ERROR;
    }
    return ActionResult.fail(errorCode);
}

private UriComponentsBuilder createUriComponentsBuilder(String relativeUrl, @Nullable Object object) {
    String url = this.baseUrl;
    if (StringUtils.hasText(relativeUrl)) {
        url += relativeUrl;
    }

    UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
    if (object != null) {
        builder.queryParams(this.convertToMultiValueMap(object));
    }

    return builder;
}

private MultiValueMap<String, String> convertToMultiValueMap(Object object) {
    //todo object to params
}

【问题讨论】:

  • 你用 @ModelAttribute 注释了吗?另外,您为什么不将其设置为 RequestBody 并使用 POST 之类的东西?
  • @RoddyoftheFrozenPeas 我不需要 ModelAttribute。我使用该对象是因为使用它比使用 RequestParam 注释更方便。为什么要获取?它具有源自方法语义的所有优点(幂等性、安全性和缓存)
  • 只有以这种方式实现才能获得幂等性和缓存。不知道你说的安全是什么意思。无论哪种方式,我都将其解释为“我想要”。无论如何,既然各种值只是美化的请求参数,那么,只需使用queryParams方法来传递各种值。
  • @RoddyoftheFrozenPeas 问题是如何从对象中获取queryParams,不要手动将对象映射到这些参数

标签: java spring resttemplate


【解决方案1】:

我不知道为什么我投了反对票。但是我尝试进行我的实现,也许某处可能会出现无法预料的错误。

public static LinkedMultiValueMap<String, String> toUrlParams(Object value) {
    final LinkedMultiValueMap<String, String> params = new LinkedMultiValueMap<>();
    final List<Field> declaredFields = getAllFields(value);
    for (Field field : declaredFields) {
        ReflectionUtils.makeAccessible(field);
        final String name = field.getName();
        final Object fieldVal = ReflectionUtils.getField(field, value);
        mapFields(params, name, fieldVal);
    }
    return params;
}

private static List<Field> getAllFields(Object t) {
    final List<Field> fields = new ArrayList<>();
    Class clazz = t.getClass();
    while (clazz.getSuperclass() != null) {
        final Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            final int modifiers = field.getModifiers();
            if (!(Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers))) {
                fields.add(field);
            }
        }
        clazz = clazz.getSuperclass();
    }
    return fields;
}

private static void mapFields(LinkedMultiValueMap<String, String> params,
                              String fieldName,
                              @Nullable Object fieldVal) {
    if (fieldVal != null) {
        final Class<?> fieldClass = fieldVal.getClass();
        if (BeanUtils.isSimpleValueType(fieldClass) || fieldVal instanceof Number || fieldVal instanceof UUID) {
            if (fieldClass.isEnum()) {
                params.add(fieldName, ((Enum) fieldVal).name());
            } else {
                params.add(fieldName, fieldVal.toString());
            }
        } else {
            if (fieldVal instanceof Map) {
                throw new IllegalArgumentException("Map is not allowed for url params");
            }
            if (fieldVal instanceof List) {
                final Iterator iterator = ((Iterable) fieldVal).iterator();
                int i = 0;
                while (iterator.hasNext()) {
                    final Object iterElement = iterator.next();
                    mapFields(params, fieldName + "[" + i + "]", iterElement);
                    i++;
                }
            } else {
                if (fieldVal instanceof Set) {
                    for (Object iterElement : ((Iterable) fieldVal)) {
                        mapFields(params, fieldName, iterElement);
                    }
                } else {
                    if (fieldVal instanceof Collection) {
                        throw new IllegalArgumentException("Unknown collection, expected List or Set, but was " + fieldVal.getClass());
                    }
                    if (fieldVal.getClass().isArray()) {
                        final int length = Array.getLength(fieldVal);
                        for (int i = 0; i < length; i++) {
                            Object arrayElement = Array.get(fieldVal, i);
                            mapFields(params, fieldName + "[" + i + "]", arrayElement);
                        }
                    } else {
                        final List<Field> declaredFields = getAllFields(fieldVal);

                        for (Field field : declaredFields) {
                            ReflectionUtils.makeAccessible(field);
                            final String name = field.getName();
                            final Object nestedField = ReflectionUtils.getField(field, fieldVal);
                            mapFields(params, fieldName + "." + name, nestedField);
                        }
                    }
                }
            }
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-26
    • 2017-06-26
    • 1970-01-01
    • 2017-07-02
    • 2022-01-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多