Spring MVC 数据绑定(一)

2019-11-12  本文已影响0人  Snipers_onk

写在最前

本文主要了解请求参数解析的过程,源码参考为4.2.5,为了便于理解,部分代码我进行了简化。如果需要了解整体请查看Spring MVC源码。
数据类型的转换和校验在下一篇: Spring MVC 数据绑定(二)

引子

问题:Spring MVC的Controller是如何将参数和前端传来的数据一一对应的?
在Spring MVC中的Controller,参数来源有多种,包括:

所以不同的参数要使用不同的ArgumentResolver进行解析,ArgumentResolver是在bean加载的初始化过程中加载的。

ArgumentResolver的初始化

ArgumentResolver的初始化是在RequestMappingHandlerAdapter的bean完成属性注入之后,实现了的InitializingBeanafterPropertiesSet方法中完成的。

@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);
}

到这里,请求的数据就被转换为方法中需要的参数数组,方法就可以执行了。

总结

  1. SpringMVC初始化时,RequestMappingHandlerAdapter类会把一些默认的参数解析器添加到argumentResolvers中。当SpringMVC接收到请求后首先根据url查找对应的HandlerMethod。
  2. 遍历HandlerMethod的MethodParameter数组
  3. 根据MethodParameter的类型来查找确认使用哪个HandlerMethodArgumentResolver,遍历所有的argumentResolvers的supportsParameter(MethodParameter parameter)方法。。如果返回true,则表示查找成功,当前MethodParameter,使用该HandlerMethodArgumentResolver。这里确认大多都是根据参数的注解已经参数的Type来确认。
  4. 解析参数,从request中解析出MethodParameter对应的参数,这里解析出来的结果都是String类型。
  5. 转换参数,把对应String转换成具体方法所需要的类型,并进行校验,使用的是WebDataBinder中默认注册的Converter和Validator。
上一篇下一篇

猜你喜欢

热点阅读