您的位置:首页 > 编程语言 > Java开发

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