2011-02-23 34 views
0

我正在通过Spring配置RESTful Web服务,并使用各种表示形式(包括JSON)。我希望接口是对称的,这意味着通过GET序列化为JSON的对象的格式也是POST/PUT可以接受的格式。不幸的是,我只能让GETs工作。使用Spring MVC 3.0生成/消费对称JSON

这是我的用于发送和接收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> 

当我击控制器,其与一个GET来返回一个对象,例如,一本书,它输出这样的东西。

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

如果我转身,并通过POST重新提交一些类似的JSON或PUT,春都不能消耗它,抱怨Unrecognized field "book" (Class com.mycompany.Book), not marked as ignorable。此外,如果我剥离“书籍”包装元素(我宁愿不要,但只是为了看看会发生什么),我会得到一个400 BAD REQUEST。无论哪种情况,我的控制器代码都不会被击中。

这里是我的控制器 - 我宁愿没有任何特定于JSON的代码(或者我的类上的注释被编组/解组),因为它们将具有多个表示形式 - 我想使用Spring的解耦MVC基础结构来推动这种类型事情(编组/视图解决的/ etc。)到配置文件:

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

回答

1

虽然很尴尬,我回答我的问题留给后人:-)

原来,在我真正的代码相当于控制方法,通过这个例子的方法,我贴表示:

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

其实看起来更像这个(注:LongString):

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

而且值为PA ssed不能转换为Long(它是字母数字)。所以......我搞砸了! :-)

然而,春天不是很好,只是吐出400 Bad Request。我附加了一个调试器来发现这一点。

使用ModelAndView仍然生成一个外部包装元素,我将不得不以某种方式处理(因为我想用户ModelAndView支持JSP视图等)。我可能必须为此提供自定义视图。


更新包装元素上:

事实证明,它是由Spring创建一个编组地图表示模型对象。这张地图有一个名为“书”的关键字(从我想的类名生成,因为它在那里,即使我只是返回一本书)。这里是一个黑客的方式,直到我能找到更好的方式:

/** 
* 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

MappingJackson2JsonView具有属性extractValueFromSingleKeyModel。当它设置为true时,它的行为与您的SimpleJacksonJsonView完全相同。 – Felix 2013-10-15 15:43:38

+0

在这个问题的时候,我认为财产不存在。谢谢! – SingleShot 2013-10-17 04:11:01

0

注意您指定使用得到你的控制器的方法,这样你就可以配置特定POST方法能够接收GET和POST表单REST Full方法,类似这样:

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

谢谢。我不想“通过邮件获取”,我想将PUT和/或POST JSON对象发布到服务器(我正在创建一个RESTful服务)。我不相信问题在于如何定义我的请求映射,但我如何为传入的JSON配置消息转换器。看到我的标记为“PUT”的示例控制器方法 - 即由于消息解组中的错误而无法调用的接口。 – SingleShot 2011-02-23 18:52:17

+1

Sory @SingleShot,我不明白你的问题。那么,如果你的问题是与消息转换器,我认为你将不得不customyze自己的。其实我不喜欢Jackson在做杰森对象时所做的事情。所以我找到了一种通过构建一个custon Gson消息转换器来隐藏它的方法[看看它是否可以帮助你得到一个新想法](http://stackoverflow.com/questions/5019162/custom-httpmessageconverter-with-responsebody-to -do-json-things) – Iogui 2011-02-24 02:35:34

+0

谢谢。即使你没有回答我确切的问题,你让我了解Gson :-) – SingleShot 2011-02-24 03:38:38

0

没有包装书的@SingleShot,{“isbn”:“1234”,“作者”:“狮子座托尔斯泰”,“标题”:“战争与和平”}是JSON书籍实例的正确表示,由于您在GET方法中返回ModelAndView时所拥有的modelName“book”,因此添加了书籍包装,而MappingJacksonJSONView则附加在书包装上。

一个更好的模式是简单地用

@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

谢谢。我更喜欢你的@ResponseBody建议,但是发现使用ModelAndView是必要的,以便让我的控制器在需要HTML时解析为JSP(除JSON外还生成HTML和XML)。我确实将Content-Type设置为application/json。不过,我会探讨你的建议。 – SingleShot 2011-02-23 21:20:02

+0

我测试了你建议的样式 - 它有相同的结果:“书”包装元素。 – SingleShot 2011-02-24 05:52:19

+0

我有一个hacker解决方案的包装元素。看到我的答案。 – SingleShot 2011-02-24 07:07:52