Java 使用Spring MVC 3.0生成/使用对称JSON

Java 使用Spring MVC 3.0生成/使用对称JSON,java,json,spring,spring-mvc,marshalling,Java,Json,Spring,Spring Mvc,Marshalling,我正在通过Spring配置一个RESTful web服务,其中包含各种表示,包括JSON。我希望接口是对称的,这意味着通过GET序列化为JSON的对象的格式也是POST/PUT可以接受的格式。不幸的是,我只能去上班 以下是我发送和接收JSON的配置,它由JSON消息转换器和视图组成: <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <

我正在通过Spring配置一个RESTful web服务,其中包含各种表示,包括JSON。我希望接口是对称的,这意味着通过GET序列化为JSON的对象的格式也是POST/PUT可以接受的格式。不幸的是,我只能去上班

以下是我发送和接收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>
如果我转过身,通过POST或PUT重新提交一些类似的JSON,Spring将无法使用它,抱怨
无法识别的字段“book”(Class com.mycompany.book),没有标记为ignorable
。此外,如果我去掉“book”包装器元素(我宁愿不要,只是看看会发生什么),我会收到一个400个错误的请求。无论哪种情况,我的控制器代码都不会被命中

这是我的控制器-我不希望这里有任何特定于JSON的代码(或正在编组/解编组的类上的注释),因为它们将有多个表示形式-我希望使用Spring的解耦MVC基础结构,将这种事情(编组/视图解析/等)推送到配置文件中:

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

注意:您正在指定在控制器方法上使用GET,因此可以配置特定的POST方法,以便能够接收GET和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);
}
@SingleShot,{“isbn”:“1234”,“author”:“Leo Tolstoy”,“title”:“War and Peace”}如果没有包装器,则书籍包装器是JSON中书籍实例的正确表示形式,添加书籍包装器是因为在GET方法中返回ModelAndView时您拥有的模型名“book”,并将JacksonJSONVIEW图钉映射到书包装上

更好的模式是在Get中简单地返回book对象,并用@ResponseBody注释该方法

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

关于您的PUT未解析正确的控制器方法,您能否确认您的请求内容类型为application/json。

尽管这很尴尬,但我还是要为后代回答我自己的问题:-)

事实证明,我的实际代码中的等效控制器方法由我发布的示例方法表示:

void saveBook(@RequestBody Book book, @PathVariable String isbn)
实际上看起来更像这样(注意:
Long
vice
String
):

并且正在传递的值不能转换为长值(它是字母数字)。所以我搞砸了!:-)

然而,Spring并不是很好,只是简单地抛出了
400个错误请求
。我附加了一个调试器来发现这一点

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();
    }
}
/**
*当使用不知道介质类型的Spring控制器时,生成的模型
*对象最终作为值出现在地图中。MappingJacksonJsonView然后转换此映射
*到JSON,这(可能)错误地包装了映射中的单个模型对象
*入口的钥匙。如果只有一个模型,则此类将消除此包装器元素
*反对。
*/
公共类SimpleJacksonJsonView扩展了MappingJacksonJsonView{
@凌驾
@抑制警告(“未选中”)
受保护对象过滤器模型(映射模型){
Map filteredModel=(Map)super.filterModel(model);
如果(filteredModel.size()!=1)返回filteredModel;
返回filteredModel.entrySet().iterator().next().getValue();
}
}

谢谢。我不想“通过post获取”,我想把一个JSON对象放到服务器上(我正在做一个RESTful服务)。我认为问题不在于如何定义请求映射,而在于如何为传入的JSON配置消息转换器。请参阅我的示例控制器方法,标记为“PUT”-这是由于消息解组错误而无法调用的接口。Sory@SingleShot,我想我不理解您的问题。好吧,如果你的问题是与信息转换器,我想你将不得不定制自己的。事实上,我不喜欢Jackson在处理jason对象时所做的事情。所以我找到了一种方法,通过构建一个custon Gson消息转换器来隐藏它,谢谢。即使你没有回答我的确切问题,你还是让我了解了Gson:-)谢谢。我更喜欢您的@ResponseBody建议,但我发现在需要HTML时,使用ModelAndView可以让我的控制器解析为JSP(除了JSON之外,我还生成HTML和XML)。我确实将内容类型设置为application/json。但是,我将探讨您的建议。我测试了您建议的样式-它具有相同的结果:一个“book”包装器元素。我有一个针对包装器元素的黑客解决方案。查看我的答案。MappingJackson2JsonView有一个属性extractValueFromSingleKeyModel。当设置为true时,它的行为与SimpleChackSonJSonView完全相同。谢谢
void saveBook(@RequestBody Book book, @PathVariable Long isbn)
/**
 * 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();
    }
}