使用 Spring MVC 3.0 生成/使用对称 JSON
我正在通过 Spring 配置 RESTful Web 服务,具有各种表示形式,包括 JSON。我希望接口是对称的,这意味着通过 GET 序列化为 JSON 的对象的格式也是 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>
当我使用 GET 访问控制器以返回对象(例如,一本书)时,它会输出类似这样的内容。
{"book":{"isbn":"1234","author":"Leo Tolstoy","title":"War and Peace"}}
如果我转身并通过 POST 或 PUT 重新提交一些类似的 JSON,Spring 无法使用它,并抱怨无法识别的字段“book”(类 com.mycompany.Book),未标记为可忽略
。此外,如果我去掉“book”包装元素(我不想这样做,只是为了看看会发生什么),我会收到 400 BAD REQUEST。无论哪种情况,我的控制器代码都不会被命中。
这是我的控制器 - 我不想在这里有任何特定于 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));
}
I am configuring a RESTful web service via Spring, with various representations, including JSON. I want the interface to be symmetrical, meaning the format of an object serialized to JSON via a GET is also the format that a POST/PUT would accept. Unfortunately I can only get GETs to work.
Here's my configuration for sending and receiving JSON, which consists of a JSON message converter and view:
<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>
When I hit a controller with a GET to return an object, for example, a Book, it outputs something like this.
{"book":{"isbn":"1234","author":"Leo Tolstoy","title":"War and Peace"}}
If I turn around and re-submit some similar JSON via a POST or PUT, Spring cannot consume it, complaining about Unrecognized field "book" (Class com.mycompany.Book), not marked as ignorable
. Additionally, if I strip off the "book" wrapper element (I'd rather not, but just to see what happens), I get a 400 BAD REQUEST. In either case, my controller code is never hit.
Here's my controller - I'd rather not have any JSON-specific code here (or annotations on my classes being marshalled/unmarshalled) as they will have multiple representations - I want to use Spring's decoupled MVC infrastructure that pushes that kind of thing (marshalling/view resolving/etc.) into configuration files:
@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));
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
尽管这很尴尬,但我正在为后代回答我自己的问题:-)
事实证明,我发布的这个示例方法所代表的真实代码中的等效控制器方法:
实际上看起来更像这样(注意:
Long
副String
):并且传递的值无法转换为 Long (它是字母数字)。所以……我搞砸了! :-)
然而,Spring 对此不太友好,只是简单地抛出了
400 Bad Request
。我附加了一个调试器来发现这一点。使用 ModelAndView 仍然会生成一个外部包装元素,我必须以某种方式处理该元素(因为我想使用 ModelAndView 来支持 JSP 视图等)。我可能必须为此提供一个自定义视图。
包装器元素的更新:
事实证明,它是由 Spring 编组表示模型的对象 Map 创建的。该地图有一个名为“book”的键(我想是从类名生成的,因为即使我只是返回一本书,它也在那里)。在我找到更好的方法之前,这是一种破解方法:
Even though it is embarrassing, I am answering my own question for posterity :-)
It turns out that the equivalent controller method in my real code represented by this example method that I posted:
Actually looks more like this (note:
Long
viceString
):And the value being passed can't be converted to a Long (it is alphanumeric). So... I screwed up! :-)
However, Spring wasn't very nice about it and simply spit out
400 Bad Request
. I attached a debugger to discover this.The use of ModelAndView still generates an outer wrapper element that I will have to deal with somehow (as I want to user ModelAndView to support JSP views and such). I will probably have to provide a custom view for that.
Update on the wrapper element:
It turns out that it is created by Spring marshalling a Map of objects representing the model. This map has a key named "book" (generated from the class name I suppose because its there even if I simply return a Book). Here is a hackish way around it until I can find a better way:
请注意,您指定在控制器方法上使用 GET,因此您可以配置特定的 POST 方法,以便能够接收 GET 和 POST 形式的 RESTFull 方法,如下所示:
Note you are specifying to use GET on your controller method, so you can configure a specific POST method to be able to receive both GET and POST form RESTFull methods, Something like this:
@SingleShot,没有包装书的 {"isbn":"1234","author":"Leo Tolstoy","title":"War and Peace"} 是 JSON 中书籍实例的正确表示,书籍包装器是添加是因为当您在 GET 方法中返回 ModelAndView 时您拥有 modelName“book”,并且 MappingJacksonJSONView 附加在 book 包装器上。
更好的模式是简单地在 Get 中返回 book 对象,并使用 @ResponseBody 注释该方法。
关于 PUT 未解析正确的 Controller 方法,您能否确认您的请求内容类型为 application/json。
@SingleShot, {"isbn":"1234","author":"Leo Tolstoy","title":"War and Peace"} without the wrapper book is the correct representation for a book instance in JSON, the book wrapper is added because of the modelName "book" that you have when you are returning the ModelAndView in your GET method, and MappingJacksonJSONView tacks on the book wrapper.
A better pattern would be to simply return the book object in your Get and annotate the method with @ResponseBody
Regarding your PUT not resolving the correct Controller method, can you confirm that you have your request content type as application/json.