Spring MVC 数据绑定(一)
写在最前
本文主要了解请求参数解析的过程,源码参考为4.2.5,为了便于理解,部分代码我进行了简化。如果需要了解整体请查看Spring MVC源码。
数据类型的转换和校验在下一篇: Spring MVC 数据绑定(二)。
引子
问题:Spring MVC的Controller是如何将参数和前端传来的数据一一对应的?
在Spring MVC中的Controller,参数来源有多种,包括:
- request请求中的参数,包括url中的参数,post请求中的参数以及请求头包含的值
- cookie中的参数
- session中的参数
- 设置到FlushMap中的参数,这种参数用于Redirect的参数传递
- SessionAttribute中的参数,这类参数通过@SessionAttribute传递
- 通过相应@ModelAttribute的方法设置的参数
所以不同的参数要使用不同的ArgumentResolver进行解析,ArgumentResolver是在bean加载的初始化过程中加载的。
ArgumentResolver的初始化
ArgumentResolver
的初始化是在RequestMappingHandlerAdapter
的bean完成属性注入之后,实现了的InitializingBean
的afterPropertiesSet
方法中完成的。
@Override
public void afterPropertiesSet() {
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
...
}
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new ModelMethodProcessor());
...
// 用户自定义Resolver,用户可以直接在山下文中注册bean,Spring会自动扫描并加入到resolvers
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
return resolvers;
}
从源码看数据绑定
Spring MVC框架会将请求统一进行处理,处理的方法是DispatcherServlet#doDispatch()
方法。首先根据请求路径通过HandlertMapping找到对应的Handler,然后根据handler找到对应的HandlerAdapter
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
// 根据请求路径通过HandlertMapping找到对应的Handler
mappedHandler = getHandler(processedRequest);
// 然后根据handler找到对应的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 处理handler,返回ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
}
handleInternal
ha.handle调用的是AbstractHandlerMethodAdapter#handle()
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
具体实现在RequestMappingHandlerAdapter#handleInternal()
,方法中调用了invokeHandlerMethod()
,在此方法中,包含了对整个方法的处理。
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception{
ModelAndView mav = null;
...
mav = invokeHandlerMethod(request, response, handlerMethod);
...
return mav;
}
在RequestMappingHandlerAdapter#invokeHandlerMethod()
中,就是数据绑定的核心代码。
invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
//获取DataBinder工厂和Model工厂实例
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
//维护Model的工厂
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
/*
通过反射获得要执行的代理方法对象
将参数处理器、返回值处理器、binderFactory等引用设置到代理对象。
*/
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
//ModelAndView容器,用于保存Model和View,它贯穿了整个处理过程
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
//获取请求中所有attibute,并加入到ModelAndView容器
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
//将SessionAttribute和注释了@ModelAttribute的方法的参数设置到Model
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
//根据配置对ignoreDefaultModelOnRedirect进行了参数设置
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
//执行Controller中的方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
//返回ModelAndView容器
return getModelAndView(mavContainer, modelFactory, webRequest);
}
public void invokeAndHandle(ServletWebRequest webRequest,
ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
...
}
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
//从request中解析出HandlerMethod方法所需要的参数,并返回Object[]
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
//通过反射执行HandleMethod中的method,方法参数为args。并返回方法执行的返回值
Object returnValue = doInvoke(args);
return returnValue;
}
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
//获取方法参数数组
MethodParameter[] parameters = getMethodParameters();
//创建一个参数数组,保存从request解析出的方法参数
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
/*
为参数设置参数名解析器,这个解析器集合优先使用Java 8反射机制解析参数名,
如果解析失败,就使用基于ASM的参数解析器去获取Debug
*/
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
args[i] = resolveProvidedArgument(parameter, providedArgs);
//是否支持对参数进行解析
if (this.argumentResolvers.supportsParameter(parameter)) {
//使用HandlerMethodArgumentResolver对参数进行解析
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
//如果解析没成功,则抛出异常
if (args[i] == null) {
String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
throw new IllegalStateException(msg);
}
}
return args;
}
argumentResolvers
resolveArgument方法的实现是在argumentResolvers中对应的解析器里实现的,不同的解析器指的是在初始化过程中定义的argumentResolvers,这里举两个典型的例子
AbstractNamedValueMethodArgumentResolver
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
//获取参数的Class对象
Class<?> paramType = parameter.getParameterType();
//根据参数定义创建一个NamedValueInfo对象
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
//根据参数名解析出对象的值
Object arg = resolveName(namedValueInfo.name, parameter, webRequest);
//如果参数为null
if (arg == null) {
//如果@RequestParam 设置了defaultValue,则使用设置的值
if (namedValueInfo.defaultValue != null) {
arg = resolveDefaultValue(namedValueInfo.defaultValue);
}
//判断这个字段是否是必填,RequestParam注解默认为必填。
else if (namedValueInfo.required && !parameter.getParameterType().getName().equals("java.util.Optional")) {
handleMissingValue(namedValueInfo.name, parameter);
}
/*
处理null值,如果是基本类型:为boolean,返回为true,否则则抛出异常
不是基本类型,返回null
*/
arg = handleNullValue(namedValueInfo.name, arg, paramType);
}
//为空串,则有默认值
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveDefaultValue(namedValueInfo.defaultValue);
}
//如果DataBinder不为空,则进行数据类型转换,将String类型转换为参数类型
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;
}
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
MultipartHttpServletRequest multipartRequest =
WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
Object arg;
//下面为各种类型的处理,包括文件上传和字符串数组等,最常用的是最下面的处理逻辑
if (MultipartFile.class == parameter.getParameterType()) {
assertIsMultipartRequest(servletRequest);
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
arg = multipartRequest.getFile(name);
}
else if (isMultipartFileCollection(parameter)) {
assertIsMultipartRequest(servletRequest);
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
arg = multipartRequest.getFiles(name);
}
else if (isMultipartFileArray(parameter)) {
assertIsMultipartRequest(servletRequest);
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
List<MultipartFile> multipartFiles = multipartRequest.getFiles(name);
arg = multipartFiles.toArray(new MultipartFile[multipartFiles.size()]);
}
else if ("javax.servlet.http.Part".equals(parameter.getParameterType().getName())) {
assertIsMultipartRequest(servletRequest);
arg = servletRequest.getPart(name);
}
else if (isPartCollection(parameter)) {
assertIsMultipartRequest(servletRequest);
arg = new ArrayList<Object>(servletRequest.getParts());
}
else if (isPartArray(parameter)) {
assertIsMultipartRequest(servletRequest);
arg = RequestPartResolver.resolvePart(servletRequest);
}
else {
arg = null;
if (multipartRequest != null) {
List<MultipartFile> files = multipartRequest.getFiles(name);
if (!files.isEmpty()) {
arg = (files.size() == 1 ? files.get(0) : files);
}
}
//从请求中通过参数名称获取参数数组,然后赋值给参数
if (arg == null) {
String[] paramValues = webRequest.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
}
return arg;
}
ModelAttributeMethodProcessor
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
//获取参数名称
String name = ModelFactory.getNameForParameter(parameter);
//容器中是否已经存在对象,不存在创建对象
Object attribute = (mavContainer.containsAttribute(name) ?
mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest));
//创建WebDataBinder
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
//为WebDataBinder绑定request
bindRequestParameters(binder, webRequest);
//校验参数
validateIfApplicable(binder, parameter);
//如果结果存在错误,抛出异常
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// 在model中替换为已解析的属性,添加BindingResult
Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
//如果必要,进行类型转换
return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
到这里,请求的数据就被转换为方法中需要的参数数组,方法就可以执行了。
总结
- SpringMVC初始化时,RequestMappingHandlerAdapter类会把一些默认的参数解析器添加到argumentResolvers中。当SpringMVC接收到请求后首先根据url查找对应的HandlerMethod。
- 遍历HandlerMethod的MethodParameter数组
- 根据MethodParameter的类型来查找确认使用哪个HandlerMethodArgumentResolver,遍历所有的argumentResolvers的supportsParameter(MethodParameter parameter)方法。。如果返回true,则表示查找成功,当前MethodParameter,使用该HandlerMethodArgumentResolver。这里确认大多都是根据参数的注解已经参数的Type来确认。
- 解析参数,从request中解析出MethodParameter对应的参数,这里解析出来的结果都是String类型。
- 转换参数,把对应String转换成具体方法所需要的类型,并进行校验,使用的是WebDataBinder中默认注册的Converter和Validator。