在使用Spring MVC + Jackson-1.9.12时报的一个异常,本文暂时也只是描述一下如何绕开这个异常,并简单说明一下出错原因。希望如果有大神碰巧看过这个问题,不吝赐教。
异常详细描述
主要是Jackson的异常:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: No serializer found for class org.springframework.validation.DefaultMessageCodesResolver and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: org.springframework.validation.support.BindingAwareModelMap["org.springframework.validation.BindingResult.GStation"]->org.springframework.validation.BeanPropertyBindingResult["messageCodesResolver"]); nested exception is org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.springframework.validation.DefaultMessageCodesResolver and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: org.springframework.validation.support.BindingAwareModelMap["org.springframework.validation.BindingResult.GStation"]->org.springframework.validation.BeanPropertyBindingResult["messageCodesResolver"]) org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.writeInternal(MappingJacksonHttpMessageConverter.java:194) org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:179) org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodInvoker.writeWithMessageConverters(AnnotationMethodHandlerAdapter.java:1037) org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodInvoker.handleResponseBody(AnnotationMethodHandlerAdapter.java:995) org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodInvoker.getModelAndView(AnnotationMethodHandlerAdapter.java:944) org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:441) org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:428) org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925) org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856) org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936) org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:838) javax.servlet.http.HttpServlet.service(HttpServlet.java:710) org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812) javax.servlet.http.HttpServlet.service(HttpServlet.java:803) org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
因为使用Spring MVC框架控制器处理请求,所以同时会报Spring的异常:
org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.springframework.validation.DefaultMessageCodesResolver and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: org.springframework.validation.support.BindingAwareModelMap["org.springframework.validation.BindingResult.GStation"]->org.springframework.validation.BeanPropertyBindingResult["messageCodesResolver"]) org.codehaus.jackson.map.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:52) org.codehaus.jackson.map.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:25) org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:446) org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:150) org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112) org.codehaus.jackson.map.ser.std.MapSerializer.serializeFields(MapSerializer.java:262) org.codehaus.jackson.map.ser.std.MapSerializer.serialize(MapSerializer.java:186) org.codehaus.jackson.map.ser.std.MapSerializer.serialize(MapSerializer.java:23) org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:610) org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:256) org.codehaus.jackson.map.ObjectMapper.writeValue(ObjectMapper.java:1613) org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.writeInternal(MappingJacksonHttpMessageConverter.java:191) org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:179) org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodInvoker.writeWithMessageConverters(AnnotationMethodHandlerAdapter.java:1037) org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodInvoker.handleResponseBody(AnnotationMethodHandlerAdapter.java:995) org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodInvoker.getModelAndView(AnnotationMethodHandlerAdapter.java:944) org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:441) org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:428) org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925) org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856) org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936) org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:838) javax.servlet.http.HttpServlet.service(HttpServlet.java:710) org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812) javax.servlet.http.HttpServlet.service(HttpServlet.java:803) org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
代码
前台代码使用jQuery的ajax异步调用,代码中利用Spring MVC可以直接将data数据赋值到pojo对象属性中。
$.ajax({ type : "post", url : "/businesssite/updStation", data : station, dataType : "json", success : function(data) { if(data && data.msg == "s") { alert("成功"); } else { alert("失败"); } }, error : function(e) { alert("错误:" + e.responseText); } });
后台代码Spring MVC的注解定义控制器请求路径及参数,station为需要获取ajax参数的对象,map为需要返回的对象(此处的map默认情况下会是Spring定义的BindingAwareModelMap的实例,Map、Model、ModelMap都是一样的。但由于Model接口没有clear方法,会影响其中一种处理bug的方法,下面会讲到。)
@RequestMapping(value = "/updStation", method = POST) @ResponseBody public Map<String, Object> updStation(GStation station, Map<String, Object> map) { try { log.debug(station); map.put("msg", "s"); } catch (Exception e) { log.error(e.getMessage(), e); map.put("msg", "e"); } return map; }
此时就会报出本文开头提到的异常。
现象
通过打断点,查看进入控制器的方法中各个对象实例的状态,发现正常程序与异常程序之间的区别:
把自定义类换成Java自带的封装类都能够正确运行;
当程序正常运行时,map是空的(不是null)。而异常程序的map包含两个值,一个是GStation对象的值,一个是org.springframework.validation.BindingResult.GStation=org.springframework.validation.BeanPropertyBindingResult: 0 errors。
有图为证:
1. 出现异常的代码中map或model的值
2. 未出现异常的代码中map或model的值
因此做出大胆假设,出错的原因是ajax传递多值至对象中时,Spring MVC会在map里面增加两个值(一个是传输的数据对象,一个是对于对象属性绑定的验证结果),而这两个值是Jackson将map处理成json格式时出错 。通过Spring文档中的内容也验证了这一说法,内容如下:
Command or form objects to bind request parameters to bean properties (via setters) or directly to fields, with customizable type conversion, depending on@InitBinder methods and/or the HandlerAdapter configuration. See thewebBindingInitializer property on RequestMappingHandlerAdapter. Such command objects along with their validation results will be exposed as model attributes by default, using the command class class name - e.g. model attribute "orderAddress" for a command object of type "some.package.OrderAddress". The ModelAttribute annotation can be used on a method argument to customize the model attribute name used.
所以,只要map是空的(非null)就行了。
解决办法
既然找到了解决思路,那方法就有多种了:
通过map.clear()方法将map清空(此方法适用于Map及MedalMap);
@RequestMapping(value = "/updStation", method = POST) @ResponseBody public Map<String, Object> updStation(GStation station, Map<String, Object> map) { map.clear(); try { log.debug(station); map.put("msg", "s"); } catch (Exception e) { log.error(e.getMessage(), e); map.put("msg", "e"); } return map; }
不需要Spring帮忙创建map,自己new一个。
@RequestMapping(value = "/updStation", method = POST) @ResponseBody public Map<String, Object> updStation(GStation station) { Map<String, Object> map = new HashMap<String, Object>(); try { log.debug(station); map.put("msg", "s"); } catch (Exception e) { log.error(e.getMessage(), e); map.put("msg", "e"); } return map; }
注:Spring MVC帮助定义的对象是BindingAwareModelMap,通过源码或API可以知道,这个类是LinkedHashMap的子类,也算是HashMap的子类,所以这的地方创建HashMap对象不算错。
后语
这个异常开始的时候感觉是Jackson的异常,但是起因似乎还是Spring MVC。在Map中多出的数据是Spring MVC对于向对象中赋值时增加的校验信息,当这些校验信息提交给Jackson时,Jackson无法将其转换成json数据,所以报出异常。
如果有大神路过,跪求指正。