美文网首页
Spring MVC 数据绑定(一)

Spring MVC 数据绑定(一)

作者: Snipers_onk | 来源:发表于2019-11-12 15:23 被阅读0次

    写在最前

    本文主要了解请求参数解析的过程,源码参考为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完成属性注入之后,实现了的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。

    相关文章

      网友评论

          本文标题:Spring MVC 数据绑定(一)

          本文链接:https://www.haomeiwen.com/subject/tehqictx.html