Spring MVC 中的参数绑定
2017-07-24 20:19
309 查看
参数绑定入口
@RequestMapping(value = "/saveUser", method = {RequestMethod.POST }) public ResponseEntity<ResultData> postData(@RequestBody body1, User user, String username, String passwd){ return new ResponseEntity<>(new ResultData(ResultData.ResultState.SUCCESS, true), HttpStatus.OK); }
在上面的方法中,spring MVC框架通过获取到的http请求分别为不同的参数类型进行赋值,即参数绑定。首先确定参数绑定的入口,DispatcherServlet是处理请求的入口,在该类中获取HandlerMapping实例,其中AbstractHandlerMethodMapping加载了所有的Controller的方法,通过反射获取方法上的注解和HandlerMethod,建立url与HandlerMethod直接的关联。这样当DispatcherServlet处理请求时,就会通过url交给对应的HandlerMethod处理。
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { StringBuilder sb = new StringBuilder("Invoking ["); sb.append(getBeanType().getSimpleName()).append("."); sb.append(getMethod().getName()).append("] method with arguments "); sb.append(Arrays.asList(args)); logger.trace(sb.toString()); } Object returnValue = doInvoke(args); if (logger.isTraceEnabled()) { logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]"); } return returnValue; }
以上是InvocableHandlerMethod对请求的处理,HandlerMethod实例中包括Controller类中方法的必要信息,如Method, MethodParameter[],对应的bean等,InvocableHandlerMethod持有下面这三个实例,spring就是通过这三个实例进行的参数绑定。
private WebDataBinderFactory dataBinderFactory; private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite(); private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
HandlerMethodArgumentResolver参数解析器
按spring一贯方式,spring通过策略模式给一种功能针对不同情况提供了不同的实现。spring参数解析器对不同的参数类型给出了不同的实现,如@RequestParam、@PathVariable、@RequestBody注解的参数、Bean类型的参数、普通类型的参数等。public interface HandlerMethodArgumentResolver { boolean supportsParameter(MethodParameter parameter); Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception; }
HandlerMethodArgumentResolver是参数解析器的顶层接口,supportsParameter用于判断该解析器能够解析的参数类型,resolveArgument用于具体的解析并返回解析结果。
HandlerMethodArgumentResolverComposite保存了spring默认的参数解析器实现,一种改进的组合模式,其实现的supportsParameter方法实际是遍历持有的解析器是否支持相应的参数解析,resolveArgument是调用持有的解析器的resolveArgument方法。
1. 简单参数解析器RequestParamMethodArgumentResolver
RequestParamMethodArgumentResolver能够解析的参数类型是有RequestParam注解的参数或者简单类型的参数,对应get 方式中queryString的值和post方式中 body data的值。在重载的resolveArgument方法中,4-7行确定参数的名称,并根据参数的名称从NativeWebRequest获取参数对应的值,然后将参数值转换为MethodParameter对应的类型。这个转换操作是由DataBinder完成的,DataBinder由WebDataBinderFactory负责创建,WebDataBinderFactory是在InvocableHandlerMethod创建的。public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Class<?> paramType = parameter.getParameterType(); NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); Object arg = resolveName(namedValueInfo.name, parameter, webRequest); if (arg == null) { if (namedValueInfo.defaultValue != null) { arg = resolveDefaultValue(namedValueInfo.defaultValue); } else if (namedValueInfo.required && !parameter.getParameterType().getName().equals("java.util.Optional")) { handleMissingValue(namedValueInfo.name, parameter); } arg = handleNullValue(namedValueInfo.name, arg, paramType); } else if ("".equals(arg) && namedValueInfo.defaultValue != null) { arg = resolveDefaultValue(namedValueInfo.defaultValue); } if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); try { arg = binder.convertIfNecessary(arg, paramType, parameter); } catch (ConversionNotSupportedException ex) { throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(), namedValueInfo.name, parameter, ex.getCause()); } catch (TypeMismatchException ex) { throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(), namedValueInfo.name, parameter, ex.getCause()); } } handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg; }
2. @RequestBody和@ResponseBody注解的参数解析器RequestResponseBodyMethodProcessor
这个类还实现了HandlerMethodReturnValueHandler两个接口,用于对处理方法返回值进行处理的策略接口。resolveArgument的参数解析主要是在readWithMessageConverters方法中实现,在这个方法中利用HttpMessageConverter机制将java对象写入到HttpOutputMessage或者从HttpInputMessage读入流到java对象。最后,进行参数验证并返回。public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType()); String name = Conventions.getVariableNameForParameter(parameter); WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); if (arg != null) { validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); return arg; }
HttpMessageConverter机制
HttpMessageConverter是消息转换器的顶层接口,从接口方法名可以看到主要是成对出现的判断是否可写可读(通过接收的媒体类型判断)以及读写的方法。在servlet标准中,可以用javax.servlet.ServletRequest获取ServletInputStream和ServletOutputStream,spring分别将其转换为HttpInputMessage和HttpOutputMessage接口,可以通过getBody方法获得对应的输入流和输出流。
public interface HttpMessageConverter<T> { boolean canRead(Class<?> clazz, MediaType mediaType); boolean canWrite(Class<?> clazz, MediaType mediaType); List<MediaType> getSupportedMediaTypes(); T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
HttpMessageConverter主要实现类如下图,引用自http://www.scienjus.com/custom-http-message-converter/
3. 自定义bean类型的参数解析器ModelAttributeMethodProcessor
ModelAttributeMethodProcessor能够解析ModelAttribute注解的参数,或者非简单类型的参数。通过databinder对参数进行赋值。applyPropertyValues是databinder中的方法,获取属性访问器通过java内省的方式对参数对象中的属性进行赋值。哈哈
protected void applyPropertyValues(MutablePropertyValues mpvs) { try { // Bind request parameters onto target object. getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields()); } catch (PropertyBatchUpdateException ex) { // Use bind error processor to create FieldErrors. for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) { getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult()); } } }
属性访问器(PropertyAccessor)
PropertyAccessor是一个顶层接口,子类实现的setPropertyValue对属性进行赋值,TypeConverter是Spring类型转换体系中最顶层的接口,ConfigurablePropertyAccessor继承了这两个接口,还提供了设置ConversionService的方法,BeanWrapperImpl是这个接口的具体实现类,支持嵌套属性、索引属性(数组|集合|Map)。
BeanWrapperImpl是在applyPropertyValues的getPropertyAccessor中创建的,BeanWrapperImpl构造方法中,设置属性,创建TypeConverterDelegate,并进行目标对象的内省分析,将分析结果保存到cachedIntrospectionResults。cachedIntrospectionResults缓存了由spring封装的ExtendedBeanInfo和GenericTypeAwarePropertyDescriptor。
setPropertyValues迭代所有封装的PropertyValue,调用setPropertyValue(PropertyValue pv),这是一个递归方法,getPropertyAccessorForPropertyPath通过属性名propertyName获取当前属性的子属性,若为空则返回当前属性,接着调用getPropertyNameTokens获取封装的PropertyTokenHolder,propertyName实际是一个属性表达式,PropertyTokenHolder保存了表达式相关的属性:
(1)actualName保存当前级别属性的实际名称,为[前的字符串,
(2)canonicalName为actualName再加上[key1][key2][key3]..这种形式保存当前级别属性的实际名称,为下一个.前的字符串
(3)keys代表当前级别属性中所有位于[与]间的key或索引所组成的数组
最后都调用setPropertyValue(AbstractNestablePropertyAccessor.PropertyTokenHolder tokens, PropertyValue pv)方法赋值,此方法中支持array、map、list属性,如果属性值为空且autoGrowNestedPaths为真则创建对应的实例,如果PropertyTokenHolder的keys为空调用类型的默认构造函数。
public void setPropertyValue(PropertyValue pv) throws BeansException { AbstractNestablePropertyAccessor.PropertyTokenHolder tokens = (AbstractNestablePropertyAccessor.PropertyTokenHolder)pv.resolvedTokens; if(tokens == null) { String propertyName = pv.getName(); AbstractNestablePropertyAccessor nestedPa; try { nestedPa = this.getPropertyAccessorForPropertyPath(propertyName); } catch (NotReadablePropertyException var6) { throw new NotWritablePropertyException(this.getRootClass(), this.nestedPath + propertyName, "Nested property in path '" + propertyName + "' does not exist", var6); } tokens = this.getPropertyNameTokens(this.getFinalPath(nestedPa, propertyName)); if(nestedPa == this) { pv.getOriginalPropertyValue().resolvedTokens = tokens; } nestedPa.setPropertyValue(tokens, pv); } else { this.setPropertyValue(tokens, pv); } }
类型转换 Converter
spring在参数赋值前,需要将传入的字符串转换为目标对象的实际类型,databinder通过ConversionService实例进行类型转换(spring3之前使用PropertyEditor来转换)。具体的转换类可实现Converter 接口,它支持从一个 Object 转为另一个 Object 。ConversionService是一个顶层接口,ConverterRegistry接口用于管理具体的Converter转换类,GenericConversionService是这两个接口的实现。
public interface ConversionService { boolean canConvert(Class<?> sourceType, Class<?> targetType); <T> T convert(Object source, Class<T> targetType); boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType); Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); }
ConverterFactory 接口是一种获取Converter的方式,限制Converter转换的目标类都继承相同的父类,如StringToEnumConverterFactory,从 String 到 Enum 的转换。GenericConverter 接口支持在多个不同的原类型和目标类型之间进行转换。
参考
request参数解析器
databinder
SpringMVC中WebDataBinder的应用及原理
反射获取一个方法中的参数名(不是类型)
详解SpringMVC中Controller的方法中参数的工作原理
属性访问器(PropertyAccessor)
SpringMVC 之类型转换 Converter
相关文章推荐
- Spring MVC的各种参数绑定方式(请求参数用基础类型和包装类型的区别)(转)
- Spring MVC请求参数绑定
- Spring mvc --- 参数绑定总结
- spring mvc的数据绑定(参数绑定)
- Spring mvc的参数究竟是如何绑定的
- 关于Spring MVC同名参数绑定问题的解决方法
- Spring MVC参数绑定
- Spring MVC 参数的绑定方法
- spring mvc 参数绑定
- [Spring MVC] - SpringMVC的各种参数绑定方式
- [Spring MVC] - SpringMVC的各种参数绑定方式
- spring mvc参数绑定
- spring mvc参数绑定
- [Spring MVC] - SpringMVC的各种参数绑定方式
- Spring mvc参数绑定
- Spring MVC 不同对象的同名参数绑定
- spring mvc参数绑定
- Spring MVC 4.0下参数绑定(List类型的参数)
- [Spring MVC] - SpringMVC的各种参数绑定方式
- Spring MVC 参数自动绑定List的解决方案