一.先来了解一下DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES这个属性是什么.下面是它的API描述:
Feature that determines whether encountering of unknown properties (ones that do not map to a property, and there is no "any setter" or handler that can handle it) should result in a failure (by throwing a JsonMappingException) or not. This setting only takes effect after all other handling methods for unknown properties have been tried, and property remains unhandled.
Feature is enabled by default (meaning that a JsonMappingException will be thrown if an unknown property is encountered).
大概意思:
反序列化时,遇到未知属性(那些没有对应的属性来映射的属性,并且没有任何setter或handler来处理这样的属性)时是否引起结果失败(通过抛JsonMappingException异常).此项设置只对那些已经尝试过所有的处理方法之后并且属性还是未处理(这里未处理的意思是:最终还是没有一个对应的类属性与此属性进行映射)的未知属性才有影响.
此功能默认是启用的(意味着,如果遇到未知属性时会抛一个JsonMappingException)
举个例子:
比如一段json字符串是:{"id":null,"username":"你好","passwordXXXX":"123456"},现在想反序列化为一个User对象,User类如下:
public class User {
private Long id;
private String username;
private String password;
//setter和getter方法略
}
根据上面的字符串,有一个属性passwordXXXX,但User类的属性没有对应的passwordXXXX属性,如果使用这个字符串反序列化为User对象就会抛JsonMappingException异常.但是如果将DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES设为false,那么就会忽略passwordXXXX这个属性的解析.解析的方法如下:
public static <T> T parse(String json, Class<T> clazz,boolean failOnUnknownProperties){
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownProperties);
T result=null;
try {
result=objectMapper.readValue(json, clazz);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
调用:
String json="{\"id\":null,\"username\":\"你好\",\"passwordXXXX\":\"123456\"}";;
User result = JSON.parse(json, User.class, false);
System.out.println(result);
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/getUser")
@ResponseBody
public User getUser(@RequestBody User user) {
return user;
}
}
对于一个请求过来,user参数的解析过程大概如下:
1.org.springframework.web.servlet.DispatcherServlet#doService{
doDispatch(request, response);
}
2.org.springframework.web.servlet.DispatcherServlet#doDispatch(){
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
3.org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle(){
handleInternal(request, response, (HandlerMethod) handler);
}
4.org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal(){
invokeHandleMethod(request, response, handlerMethod);
}
5.org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandleMethod(){
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
}
6.org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle(){
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
}
7.org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest(){
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
}
8.org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues(){
args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
9.org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#resolveArgument(){
resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
10.org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#resolveArgument(){
Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
}
11.org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#readWithMessageConverters(){
super.readWithMessageConverters(inputMessage, methodParam, paramType);
}
12.org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(org.springframework.http.HttpInputMessage, org.springframework.core.MethodParameter, java.lang.reflect.Type){
genericConverter.read(targetType, contextClass, inputMessage);
}
13.org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#read(){
readJavaType(javaType, inputMessage);
}
14.
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
try {
return this.objectMapper.readValue(inputMessage.getBody(), javaType);
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
}
}
这个objectMapper就是在配置spring mvc配置(当我们没有配置时,spring mvc内部也可能自动添加默认配置)的MappingJackson2HttpMessageConverter.
具体看org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.getMessageConverters()和org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.addDefaultHttpMessageConverters()
从上面,就可以看出,怎么样从请求体里面拿出数据,反序列化为Java对象,再映射到方法的这个参数去.
这里是我使用基于java config方式配置的MappingJackson2HttpMessageConverter如下(重写WebMvcConfigurerAdapter的configureMessageConverters方法):
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
}
这里配成false,就是有个别请求参数不对应,也会将请求参数正常解析为User对象;如果配成true,如果遇到请求参数不对应,就会抛异常,返回400.
再如果添加MappingJackson2HttpMessageConverter使用默认的无参构造函数,会怎样?那么会使用Jackson2ObjectMapperBuilder.json().build()生成的objectMapper.再跟下去这个参数也会配成false.
spring 3.2.8此值默认值为true,spring 4.2.5此值默认为false.从此看来,不同版本的值不同,以后版本应该都是false.如果要判断,可以在添加MappingJackson2HttpMessageConverter时,获取objectMapper后,使用objectMapper.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)来判断