Подтвердить что ты не робот

REST с Spring и полной привязкой данных Джексона

Я использую Spring MVC для обработки запросов POST JSON. Под обложками я использую MappingJacksonHttpMessageConverter, построенный на процессоре Jackson JSON, и включен, когда вы используете управляемый аннотацией mvc:

Одна из моих служб получает список действий:

@RequestMapping(value="/executeActions", method=RequestMethod.POST)
    public @ResponseBody String executeActions(@RequestBody List<ActionImpl> actions) {
        logger.info("executeActions");
        return "ACK";
    }

Я обнаружил, что Джексон отображает requestBody в список элементов java.util.LinkedHashMap(простая привязка данных). Вместо этого я хотел бы, чтобы запрос был привязан к списку типизированных объектов (в данном случае "ActionImpl" ).

Я знаю, что это легко сделать, если вы используете Jackson ObjectMapper напрямую:

List<ActionImpl> result = mapper.readValue(src, new TypeReference<List<ActionImpl>>() { }); 

но мне было интересно, какой лучший способ достичь этого при использовании Spring MVC и MappingJacksonHttpMessageConverter. Любые подсказки?

Спасибо

4b9b3361

Ответ 1

Я подозреваю, что проблема связана с стиранием типа, то есть вместо передачи общего типа параметров, возможно, передаются только действия .getClass(); и это даст эквивалент типа List <? > .

Если это так, одна из возможностей заключается в использовании промежуточного подкласса, например:

public class ActionImplList extends ArrayList<ActionImpl> { }

потому что это будет сохранять информацию типа, даже если передан только класс. Итак:

public @ResponseBody String executeActions(@RequestBody ActionImplList actions)

сделал бы трюк. Не оптимально, но должно работать.

Я надеюсь, что кто-то с большим количеством знаний Spring MVC может пролить свет на то, почему тип параметра не передается (возможно, это ошибка?), но, по крайней мере, есть работа.

Ответ 2

Я обнаружил, что вы также можете обойти проблему стирания типа, используя массив как @RequestBody вместо коллекции. Например, будет работать следующее:

public @ResponseBody String executeActions(@RequestBody ActionImpl[] actions) { //... }

Ответ 3

Для вашей информации эта функция будет доступна в Spring 3.2 (см. https://jira.springsource.org/browse/SPR-9570)

Я только что протестировал его на текущем M2, и он работает как заклинание из коробки (нет необходимости предоставлять дополнительную аннотацию для предоставления параметризованного типа, он будет автоматически разрешен новым MessageConverter)

Ответ 4

Этот вопрос уже старый, но я думаю, что могу внести свой вклад в любом случае.

Как указывал StaxMan, это связано с стиранием типа. Это определенно должно быть возможно, потому что вы можете получить общие аргументы посредством отражения от определения метода. Однако проблема заключается в API HttpMessageConverter:

T read(Class<? extends T> clazz, HttpInputMessage inputMessage);

Здесь только метод List.class будет передан методу. Таким образом, как вы можете видеть, невозможно реализовать HttpMessageConverter, который вычисляет реальный тип, просматривая тип параметра метода, поскольку он недоступен.

Тем не менее, можно запрограммировать свой собственный метод обхода - вы просто не будете использовать HttpMessageConverter. Spring MVC позволяет вам написать собственный WebArgumentResolver, который срабатывает перед стандартными методами разрешения. Например, вы можете использовать свою собственную аннотацию (@JsonRequestBody?), Которая напрямую использует ObjectMapper для анализа вашего значения. Вы сможете указать тип параметра из метода:

final Type parameterType= method.getParameterTypes()[index];
List<ActionImpl> result = mapper.readValue(src, new TypeReference<Object>>() {
    @Override
    public Type getType() {
        return parameterType;
    }
});

Не так, как предполагалось использовать TypeReference, я предполагаю, но ObjectMapper не предоставляет более подходящего метода.

Ответ 5

Вы пробовали объявить метод как:

executeActions(@RequestBody TypeReference<List<ActionImpl>> actions)

Я не пробовал, но на основании вашего вопроса это первое, что я попробую.