Java 使用Spring MVC 3.0生成/使用对称JSON
我正在通过Spring配置一个RESTful web服务,其中包含各种表示,包括JSON。我希望接口是对称的,这意味着通过GET序列化为JSON的对象的格式也是POST/PUT可以接受的格式。不幸的是,我只能去上班 以下是我发送和接收JSON的配置,它由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"> <
<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
viceString
):
并且正在传递的值不能转换为长值(它是字母数字)。所以我搞砸了!:-)
然而,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();
}
}