Создание / использование симметричного JSON с Spring MVC 3.0 - PullRequest
0 голосов
/ 23 февраля 2011

Я настраиваю веб-сервис RESTful через Spring с различными представлениями, включая JSON.Я хочу, чтобы интерфейс был симметричным, то есть формат объекта, сериализованного в JSON через GET, также является форматом, который может принять POST / PUT.К сожалению, я могу заставить работать только GET.

Вот моя конфигурация для отправки и получения JSON, которая состоит из конвертера сообщений JSON и вида:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <util:list>
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
        </util:list>
    </property>
</bean>

<bean id="contentNegotiatingViewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
    <property name="mediaTypes">
        <util:map>
            <entry key="json" value="application/json"/>
        </util:map>
    </property>
    <property name="defaultViews">
        <util:list>
            <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/>
        </util:list>
    </property>
</bean>

Когда я нажимаю на контроллер сПОЛУЧИТЬ вернуть объект, например, книгу, он выводит что-то вроде этого.

{"book":{"isbn":"1234","author":"Leo Tolstoy","title":"War and Peace"}}

Если я оборачиваюсь и повторно отправляю какой-то похожий JSON через POST или PUT, Spring не может его использовать, жалуясьоколо Unrecognized field "book" (Class com.mycompany.Book), not marked as ignorable.Кроме того, если я уберу элемент обертки «book» (я бы предпочел, но не для того, чтобы посмотреть, что произойдет), я получу 400 BAD REQUEST.В любом случае мой код контроллера никогда не срабатывает.

Вот мой контроллер - я бы предпочел, чтобы здесь не было никакого кода, специфичного для JSON (или аннотации на мои классы, маршалируемые / не маршалируемые), поскольку они будут иметь несколько представлений- Я хочу использовать несвязанную инфраструктуру MVC Spring, которая помещает такие вещи (сортировка / разрешение представления / и т. Д.) В файлы конфигурации:

@RequestMapping(method=PUT, value="/books/{isbn}")
@ResponseStatus(NO_CONTENT)
public void saveBook(@RequestBody Book book, @PathVariable String isbn) {
    book.setIsbn(isbn);
    bookService.saveBook(book)
}

@RequestMapping(method=GET, value="/books/{isbn}")
public ModelAndView getBook(@PathVariable String isbn) {
    return new ModelAndView("books/show", "book", bookService.getBook(isbn));
}

Ответы [ 3 ]

1 голос
/ 24 февраля 2011

Даже если это неловко, я отвечаю на свой вопрос для потомков: -)

Оказывается, что эквивалентный метод контроллера в моем реальном коде представлен этим методом, который я опубликовал:

void saveBook(@RequestBody Book book, @PathVariable String isbn)

На самом деле выглядит примерно так (примечание: Long тиски String):

void saveBook(@RequestBody Book book, @PathVariable Long isbn)

И передаваемое значение не может быть преобразовано в Long (это буквенно-цифровое значение). Итак ... Я облажался! : -)

Тем не менее, Spring не очень хорошо об этом и просто выплюнул 400 Bad Request. Я открыл для этого отладчик.

Использование ModelAndView по-прежнему генерирует внешний элемент-обертку, с которым мне придется как-то иметь дело (поскольку я хочу использовать ModelAndView для поддержки представлений JSP и тому подобное). Мне, вероятно, придется предоставить для этого собственное представление.


Обновление элемента обертки:

Оказывается, он создается Spring, собирая карту объектов, представляющих модель. Эта карта имеет ключ с именем «book» (сгенерированный из имени класса, я полагаю, потому что он есть, даже если я просто верну книгу). Вот обходной путь, пока я не найду лучший способ:

/**
 * When using a Spring Controller that is ignorant of media types, the resulting model
 * objects end up in a map as values. The MappingJacksonJsonView then converts this map
 * to JSON, which (possibly) incorrectly wraps the single model object in the map
 * entry's key. This class eliminates this wrapper element if there is only one model
 * object.
 */
public class SimpleJacksonJsonView extends MappingJacksonJsonView {

    @Override
    @SuppressWarnings("unchecked")
    protected Object filterModel(Map<String, Object> model) {
        Map<String, Object> filteredModel = (Map<String, Object>) super.filterModel(model);
        if(filteredModel.size() != 1) return filteredModel;
        return filteredModel.entrySet().iterator().next().getValue();
    }
}
0 голосов
/ 23 февраля 2011

@ SingleShot, {"isbn": "1234", "author": "Leo Tolstoy", "title": "War and Peace"} без книги-оболочки - правильное представление для экземпляра книги в JSON, Оболочка книги добавляется из-за "book" modelName, которое имеется у вас при возврате ModelAndView в вашем методе GET, а MappingJacksonJSONView привязывается к обертке книги.

Лучшим вариантом было бы просто вернуть объект книги в вашем Get и аннотировать метод с помощью @ ResponseBody

    @RequestMapping(method=RequestMethod.GET, value="/books/{isbn}")
public @ResponseBody Book getBook(@PathVariable String isbn) {
    return new Book("isbn","title","author");
}

Относительно того, что ваш PUT не разрешает правильный метод контроллера, можете ли вы подтвердить, что у вас есть тип содержимого вашего запроса как application / json.

0 голосов
/ 23 февраля 2011

Обратите внимание, что вы указываете использовать GET в своем методе контроллера, поэтому вы можете настроить конкретный метод POST, чтобы иметь возможность получать методы GEST и POST в форме RESTFull, что-то вроде этого:

@RequestMapping(method=GET, value="/books/{isbn}")
public ModelAndView getBook(@PathVariable String isbn) {
    return new ModelAndView("books/show", "book", bookService.getBook(isbn));
}

@RequestMapping(method=POST, value="/books/{isbn}")
public ModelAndView getByPostBook(@PathVariable String isbn) {
    return getBook(isbn);
}
...