首页 > 解决方案 > Spring无法从复杂对象构造查询参数以用于REST模板

问题描述

这个问题和Spring 的 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
}

标签: javaspringresttemplate

解决方案


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

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);
                        }
                    }
                }
            }
        }
    }
}

推荐阅读