30

springmvc 请求参数解析细节

 2年前 阅读数 31
以下为 快照 页面,建议前往来源网站查看,会有更好的阅读体验。
原文链接: http://www.cnblogs.com/yougewe/p/10396917.html?amp%3Butm_medium=referral

springmvc 的请求流程,相信大家已经很熟悉了,不熟悉的同学可以参考下资料!

有了整体流程的概念,是否对其中的实现细节就很清楚呢?我觉得不一定,比如:单是参数解析这块,就是个大学问呢?

首先,我们从最靠近请求末端的地方说起!此时,handler已经找到,即将进行处理!

这是在 RequestMappingHandlerAdapter 的处理方法 handleInternal(), 将请求交给业务代码的地方!

以下是 @ModelAttributeMethodProcessor进行处理的参数处理堆栈:


"http-nio-8080-exec-2@2414" daemon prio=5 tid=0x21 nid=NA runnable
  java.lang.Thread.State: RUNNABLE
      at org.springframework.beans.BeanWrapperImpl$BeanPropertyHandler.setValue(BeanWrapperImpl.java:358)
      at org.springframework.beans.AbstractNestablePropertyAccessor.processLocalProperty(AbstractNestablePropertyAccessor.java:469)
      at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:292)
      at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:280)
      at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:95)
      at org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.java:859)
      at org.springframework.validation.DataBinder.doBind(DataBinder.java:755)
      at org.springframework.web.bind.WebDataBinder.doBind(WebDataBinder.java:192)
      at org.springframework.web.bind.ServletRequestDataBinder.bind(ServletRequestDataBinder.java:106)
      at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.bindRequestParameters(ServletModelAttributeMethodProcessor.java:152)
      at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:113)
      at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
      at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158)
      at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128)
      at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
      at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
      at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
      at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
      at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
      at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
      at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
      at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
      at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
      at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
      at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
      at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
      at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
      at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
      at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
      at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
      at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
      at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650)
      at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
      at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
      at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
      at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
      at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
      at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
      at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
      - locked <0x19d0> (a org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper)
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
      at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
      at java.lang.Thread.run(Thread.java:745)

View Code
    
    // org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
    @Override
    protected ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ModelAndView mav;
        checkRequest(request);

        // Execute invokeHandlerMethod in synchronized block if required.
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    mav = invokeHandlerMethod(request, response, handlerMethod);
                }
            }
            else {
                // No HttpSession available -> no mutex necessary
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // No synchronization on session demanded at all...
            // 更多的时候,我们都是无状态请求的,所以都会走这里,进行业务接入
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }

        if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
            if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            }
            else {
                prepareResponse(response);
            }
        }

        return mav;
    }

    
    // 然后交给 RequestMappingHandlerAdapter 处理
    /**
     * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
     * if view resolution is required.
     * @since 4.2
     * @see #createInvocableHandlerMethod(HandlerMethod)
     */
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        // 将request和response封装为一个类,方便后续使用
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        try {
            // 使用 binderFactory 进行参数解析,该 binderFactory 将会伴随整个参数的一生
            // 这个binderFactory 与 方法和参数关联
            // 然后根据 binderFactory 获取 modelFactory, modelFactory 会绑定一些属性和参数解析器到里面,备用
            WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

            // 创建 最终可用于调用业务方法的包装
            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            invocableMethod.setDataBinderFactory(binderFactory);
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

            // 作为返回属性的一个容器
            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            modelFactory.initModel(webRequest, mavContainer, invocableMethod);
            mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

            AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
            asyncWebRequest.setTimeout(this.asyncRequestTimeout);

            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.setTaskExecutor(this.taskExecutor);
            asyncManager.setAsyncWebRequest(asyncWebRequest);
            asyncManager.registerCallableInterceptors(this.callableInterceptors);
            asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

            if (asyncManager.hasConcurrentResult()) {
                Object result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
                if (logger.isDebugEnabled()) {
                    logger.debug("Found concurrent result value [" + result + "]");
                }
                invocableMethod = invocableMethod.wrapConcurrentResult(result);
            }

            // 进一步调用 handler 方法,进入参数解析状态
            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            if (asyncManager.isConcurrentHandlingStarted()) {
                return null;
            }

            return getModelAndView(mavContainer, modelFactory, webRequest);
        }
        finally {
            webRequest.requestCompleted();
        }
    }

    private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
        Class<?> handlerType = handlerMethod.getBeanType();
        Set<Method> methods = this.initBinderCache.get(handlerType);
        if (methods == null) {
            methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
            this.initBinderCache.put(handlerType, methods);
        }
        List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
        // Global methods first
        for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache.entrySet()) {
            if (entry.getKey().isApplicableToBeanType(handlerType)) {
                Object bean = entry.getKey().resolveBean();
                for (Method method : entry.getValue()) {
                    initBinderMethods.add(createInitBinderMethod(bean, method));
                }
            }
        }
        for (Method method : methods) {
            Object bean = handlerMethod.getBean();
            initBinderMethods.add(createInitBinderMethod(bean, method));
        }
        return createDataBinderFactory(initBinderMethods);
    }

    private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
        SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
        Class<?> handlerType = handlerMethod.getBeanType();
        Set<Method> methods = this.modelAttributeCache.get(handlerType);
        if (methods == null) {
            methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
            this.modelAttributeCache.put(handlerType, methods);
        }
        List<InvocableHandlerMethod> attrMethods = new ArrayList<InvocableHandlerMethod>();
        // Global methods first
        for (Entry<ControllerAdviceBean, Set<Method>> entry : this.modelAttributeAdviceCache.entrySet()) {
            if (entry.getKey().isApplicableToBeanType(handlerType)) {
                Object bean = entry.getKey().resolveBean();
                for (Method method : entry.getValue()) {
                    attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
                }
            }
        }
        for (Method method : methods) {
            Object bean = handlerMethod.getBean();
            attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
        }
        return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
    }

    // org.springframework.web.method.support.InvocableHandlerMethod
    /**
     * Invoke the method and handle the return value through one of the
     * configured {@link HandlerMethodReturnValueHandler}s.
     * @param webRequest the current request
     * @param mavContainer the ModelAndViewContainer for this request
     * @param providedArgs "given" arguments matched by type (not resolved)
     */
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        // 此处只处理返回值问题,具体的调用逻辑再封装, 其中 providedArgs 此时仅是空值,将会在后续处理
        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        setResponseStatus(webRequest);

        if (returnValue == null) {
            if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
                mavContainer.setRequestHandled(true);
                return;
            }
        }
        else if (StringUtils.hasText(getResponseStatusReason())) {
            mavContainer.setRequestHandled(true);
            return;
        }

        mavContainer.setRequestHandled(false);
        try {
            this.returnValueHandlers.handleReturnValue(
                    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
        }
        catch (Exception ex) {
            if (logger.isTraceEnabled()) {
                logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
            }
            throw ex;
        }
    }
    

以上大体调用流程,下面我们来看下参数解析:

    /**
     * Invoke the method after resolving its argument values in the context of the given request.
     * <p>Argument values are commonly resolved through {@link HandlerMethodArgumentResolver}s.
     * The {@code providedArgs} parameter however may supply argument values to be used directly,
     * i.e. without argument resolution. Examples of provided argument values include a
     * {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.
     * Provided argument values are checked before argument resolvers.
     * @param request the current request
     * @param mavContainer the ModelAndViewContainer for this request
     * @param providedArgs "given" arguments matched by type, not resolved
     * @return the raw value returned by the invoked method
     * @exception Exception raised if no suitable argument resolver can be found,
     * or if the method raised an exception
     */
    public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        // 真正的参数解析在此处开始,是本文关心的点, 此时 providedArgs 还是为空的
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
                    "' with arguments " + Arrays.toString(args));
        }
        Object returnValue = doInvoke(args);
        if (logger.isTraceEnabled()) {
            logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
                    "] returned [" + returnValue + "]");
        }
        return returnValue;
    }

真正的参数解析如下:

    /**
     * Get the method argument values for the current request.
     */
    private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        MethodParameter[] parameters = getMethodParameters();
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = resolveProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            // 依次遍历解析器,只要有一个支持解析,则进行解析逻辑,否则抛出异常
            if (this.argumentResolvers.supportsParameter(parameter)) {
                try {
                    // 进行具体解析器的解析,当然,这里是解析器列表,调用后会再进行合适解析器的筛选过程
                    args[i] = this.argumentResolvers.resolveArgument(
                            parameter, mavContainer, request, this.dataBinderFactory);
                    continue;
                }
                catch (Exception ex) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
                    }
                    throw ex;
                }
            }
            if (args[i] == null) {
                throw new IllegalStateException("Could not resolve method parameter at index " +
                        parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() +
                        ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
            }
        }
        return args;
    }

可以看出,参数解析主要分为几步:

1. 获取参数类型数组,供后续填充使用;

2. 对每个参数类型,单独调用参数解析过程;

3. 在进行具体解析之前,先尝试简单解析是否成功(类似于读取缓存),不成功则再进行深度解析;

4. 深度解析交由所有解析器进行处理, 即 HandlerMethodArgumentResolverComposite, 它包含了所有的解析器;

    // org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
    /**
     * Iterate over registered {@link HandlerMethodArgumentResolver}s and invoke the one that supports it.
     * @throws IllegalStateException if no suitable {@link HandlerMethodArgumentResolver} is found.
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        // 获取具体适用的解析器,即调用其 supportsParameter(param) 方法判断即可
        HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
        }
        // 解析器获取到后,就交由具体的解析器进行解析了,这里有很多的分支了,咱们可以挑选几个来看一下就可以了
        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }

下面,我我们看一下 @ModelAttribute 修饰的参数解析方法(注意: 这里的解析只是针对任意一个参数的解析,所以无需关注整体参数形式):

    // org.springframework.web.method.annotation.ModelAttributeMethodProcessor    
    /**
     * Resolve the argument from the model or if not found instantiate it with
     * its default if it is available. The model attribute is then populated
     * with request values via data binding and optionally validated
     * if {@code @java.validation.Valid} is present on the argument.
     * @throws BindException if data binding and validation result in an error
     * and the next method parameter is not of type {@link Errors}.
     * @throws Exception if WebDataBinder initialization fails.
     */
    @Override
    public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        // 获取参数名字, 如: User user 获取的结果为 user ; 其实现原理为获取该注解的 value 值
        // 如果缓存中没有该参数的解析,那么就实例化一个参数实例,并放入缓存
        String name = ModelFactory.getNameForParameter(parameter);
        Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) :
                createAttribute(name, parameter, binderFactory, webRequest));

        if (!mavContainer.isBindingDisabled(name)) {
            ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
            if (ann != null && !ann.binding()) {
                mavContainer.setBindingDisabled(name);
            }
        }

        // 将参数包装进 WebDataBinder 中,方便统一操作, 将参数实例放入到 target 字段域中
        WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
        if (binder.getTarget() != null) {
            // 进行参数的解析过程, 将由 ServletModelAttributeMethodProcessor 处理
            if (!mavContainer.isBindingDisabled(name)) {
                bindRequestParameters(binder, webRequest);
            }
            validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                throw new BindException(binder.getBindingResult());
            }
        }

        // Add resolved attribute and BindingResult at the end of the model
        Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
        mavContainer.removeAttributes(bindingResultModel);
        mavContainer.addAllAttributes(bindingResultModel);

        return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
    }
    // 如下为获取参数名的实现方式,如果指明 value, 则直接获取, 否则生成一个参数简称名的字段, 注意此处的名字并不一定是 参数真正的命名
    // 其实函数的调用只要参数位置正确即可,并不需要真实的变量名
    public static String getNameForParameter(MethodParameter parameter) {
        ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
        String name = (ann != null ? ann.value() : null);
        return (StringUtils.hasText(name) ? name : Conventions.getVariableNameForParameter(parameter));
    }
    // org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
    /**
     * This implementation downcasts {@link WebDataBinder} to
     * {@link ServletRequestDataBinder} before binding.
     * @see ServletRequestDataBinderFactory
     */
    @Override
    protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
        // 取出request 对象,因为只有 request 中有需要处理的参数,此时和 response 是确认无关的
        ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
        ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
        // 调用自身的 binder 处理
        servletBinder.bind(servletRequest);
    }
    
    
    /**
     * Bind the parameters of the given request to this binder's target,
     * also binding multipart files in case of a multipart request.
     * <p>This call can create field errors, representing basic binding
     * errors like a required field (code "required"), or type mismatch
     * between value and bean property (code "typeMismatch").
     * <p>Multipart files are bound via their parameter name, just like normal
     * HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property,
     * invoking a "setUploadedFile" setter method.
     * <p>The type of the target property for a multipart file can be MultipartFile,
     * byte[], or String. The latter two receive the contents of the uploaded file;
     * all metadata like original file name, content type, etc are lost in those cases.
     * @param request request with parameters to bind (can be multipart)
     * @see org.springframework.web.multipart.MultipartHttpServletRequest
     * @see org.springframework.web.multipart.MultipartFile
     * @see #bind(org.springframework.beans.PropertyValues)
     */
    public void bind(ServletRequest request) {
        // 将所有请求参数封装到 mpvs, 使后续可以直接获取, 其实现为 使用 ArrayList 来保存属性,参考: request.getParameterNames()
        MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
        MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
        if (multipartRequest != null) {
            bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
        }
        // 此处作为扩展点,预留
        addBindValues(mpvs, request);
        // 调用webDataBinder 设值到参数绑定
        doBind(mpvs);
    }
    
    // org.springframework.web.bind.WebDataBinder
    /**
     * This implementation performs a field default and marker check
     * before delegating to the superclass binding process.
     * @see #checkFieldDefaults
     * @see #checkFieldMarkers
     */
    @Override
    protected void doBind(MutablePropertyValues mpvs) {
        // 检查默认值和 marker 设置
        checkFieldDefaults(mpvs);
        checkFieldMarkers(mpvs);
        // 调用父类实现数据 绑定,其实就是反射调用字段
        super.doBind(mpvs);
    }
    
    // DataBinder.doBind()
    /**
     * Actual implementation of the binding process, working with the
     * passed-in MutablePropertyValues instance.
     * @param mpvs the property values to bind,
     * as MutablePropertyValues instance
     * @see #checkAllowedFields
     * @see #checkRequiredFields
     * @see #applyPropertyValues
     */
    protected void doBind(MutablePropertyValues mpvs) {
        checkAllowedFields(mpvs);
        checkRequiredFields(mpvs);
        // 处理值
        applyPropertyValues(mpvs);
    }
    
    
    /**
     * Apply given property values to the target object.
     * <p>Default implementation applies all of the supplied property
     * values as bean property values. By default, unknown fields will
     * be ignored.
     * @param mpvs the property values to be bound (can be modified)
     * @see #getTarget
     * @see #getPropertyAccessor
     * @see #isIgnoreUnknownFields
     * @see #getBindingErrorProcessor
     * @see BindingErrorProcessor#processPropertyAccessException
     */
    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());
            }
        }
    }
    
    @Override
    public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
            throws BeansException {

        List<PropertyAccessException> propertyAccessExceptions = null;
        // 此处获取参数对象的n个字段为列表,以进行循环赋值, 即循环的依据是外部传入多少值,而不是参数实例有多少字段
        List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
                ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
        // 多个参数值给定时,循环设值即可
        for (PropertyValue pv : propertyValues) {
            try {
                // This method may throw any BeansException, which won't be caught
                // here, if there is a critical failure such as no matching field.
                // We can attempt to deal only with less serious exceptions.
                // 对单个 field 设值
                setPropertyValue(pv);
            }
            catch (NotWritablePropertyException ex) {
                if (!ignoreUnknown) {
                    throw ex;
                }
                // Otherwise, just ignore it and continue...
            }
            catch (NullValueInNestedPathException ex) {
                if (!ignoreInvalid) {
                    throw ex;
                }
                // Otherwise, just ignore it and continue...
            }
            catch (PropertyAccessException ex) {
                if (propertyAccessExceptions == null) {
                    propertyAccessExceptions = new LinkedList<PropertyAccessException>();
                }
                propertyAccessExceptions.add(ex);
            }
        }

    // org.springframework.beans.AbstractNestablePropertyAccessor
    @Override
    public void setPropertyValue(PropertyValue pv) throws BeansException {
        PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens;
        if (tokens == null) {
            String propertyName = pv.getName();
            AbstractNestablePropertyAccessor nestedPa;
            try {
                nestedPa = getPropertyAccessorForPropertyPath(propertyName);
            }
            catch (NotReadablePropertyException ex) {
                throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
                        "Nested property in path '" + propertyName + "' does not exist", ex);
            }
            tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
            if (nestedPa == this) {
                pv.getOriginalPropertyValue().resolvedTokens = tokens;
            }
            nestedPa.setPropertyValue(tokens, pv);
        }
        else {
            setPropertyValue(tokens, pv);
        }
    }

    // 对字段特殊处理,否则直接设值
    protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
        if (tokens.keys != null) {
            processKeyedProperty(tokens, pv);
        }
        else {
            processLocalProperty(tokens, pv);
        }
    }
    
    private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
        PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
        if (ph == null || !ph.isWritable()) {
            if (pv.isOptional()) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Ignoring optional value for property '" + tokens.actualName +
                            "' - property not found on bean class [" + getRootClass().getName() + "]");
                }
                return;
            }
            else {
                throw createNotWritablePropertyException(tokens.canonicalName);
            }
        }

        Object oldValue = null;
        try {
            Object originalValue = pv.getValue();
            Object valueToApply = originalValue;
            if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
                if (pv.isConverted()) {
                    valueToApply = pv.getConvertedValue();
                }
                else {
                    if (isExtractOldValueForEditor() && ph.isReadable()) {
                        try {
                            oldValue = ph.getValue();
                        }
                        catch (Exception ex) {
                            if (ex instanceof PrivilegedActionException) {
                                ex = ((PrivilegedActionException) ex).getException();
                            }
                            if (logger.isDebugEnabled()) {
                                logger.debug("Could not read previous value of property '" +
                                        this.nestedPath + tokens.canonicalName + "'", ex);
                            }
                        }
                    }
                    valueToApply = convertForProperty(
                            tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
                }
                pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
            }
            // 经过各种判定后,终于调用反射了
            ph.setValue(this.wrappedObject, valueToApply);
        }
        catch (TypeMismatchException ex) {
            throw ex;
        }
        catch (InvocationTargetException ex) {
            PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(
                    this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
            if (ex.getTargetException() instanceof ClassCastException) {
                throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException());
            }
            else {
                Throwable cause = ex.getTargetException();
                if (cause instanceof UndeclaredThrowableException) {
                    // May happen e.g. with Groovy-generated methods
                    cause = cause.getCause();
                }
                throw new MethodInvocationException(propertyChangeEvent, cause);
            }
        }
        catch (Exception ex) {
            PropertyChangeEvent pce = new PropertyChangeEvent(
                    this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
            throw new MethodInvocationException(pce, ex);
        }
    }


        // org.springframework.beans.BeanWrapperImpl$BeanPropertyHandler
        @Override
        public void setValue(final Object object, Object valueToApply) throws Exception {
            final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?
                    ((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :
                    this.pd.getWriteMethod());
            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) {
                if (System.getSecurityManager() != null) {
                    AccessController.doPrivileged(new PrivilegedAction<Object>() {
                        @Override
                        public Object run() {
                            writeMethod.setAccessible(true);
                            return null;
                        }
                    });
                }
                else {
                    writeMethod.setAccessible(true);
                }
            }
            final Object value = valueToApply;
            if (System.getSecurityManager() != null) {
                try {
                    AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                        @Override
                        public Object run() throws Exception {
                            writeMethod.invoke(object, value);
                            return null;
                        }
                    }, acc);
                }
                catch (PrivilegedActionException ex) {
                    throw ex.getException();
                }
            }
            else {
                // java.lang.reflect.Method, 反射设值
                writeMethod.invoke(getWrappedInstance(), value);
            }
        }

所以,对于 @ModelAttribute 参数解析,其实主要有几步:

1. 获取 request 中的参数,封装到 MutablePropertyValues 中;

2. 依次检查每个字段域的有效性;

3. 处理字段类型的转换,如:将 String 转换为 int;

4. 获取字段的反射方法,进行字段值的绑定;

5. 循环设置,直到所有字段都检查完;

6. 验证字段的有效性,如有异常,抛出;

7. 做最后的数据类型确认,如果进一步处理,则转换;(conversionService)

        
    // 验证字段有效性,如有必要的话, 以 Valid* 开头即符合验证前提
    /**
     * Validate the model attribute if applicable.
     * <p>The default implementation checks for {@code @javax.validation.Valid},
     * Spring's {@link org.springframework.validation.annotation.Validated},
     * and custom annotations whose name starts with "Valid".
     * @param binder the DataBinder to be used
     * @param methodParam the method parameter
     */
    protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {
        Annotation[] annotations = methodParam.getParameterAnnotations();
        for (Annotation ann : annotations) {
            Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
            // 只要以 Valid 打头的验证都可以,即做到 spring 自身的验证方式有效,但是也兼容其他框架的验证方式
            if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
                Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
                Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
                // 调用验证方式,一般为 调用其 validate() 方法;
                binder.validate(validationHints);
                break;
            }
        }
    }
    
    /**
     * Invoke the specified Validators, if any, with the given validation hints.
     * <p>Note: Validation hints may get ignored by the actual target Validator.
     * @param validationHints one or more hint objects to be passed to a {@link SmartValidator}
     * @see #setValidator(Validator)
     * @see SmartValidator#validate(Object, Errors, Object...)
     */
    public void validate(Object... validationHints) {
        for (Validator validator : getValidators()) {
            if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
                ((SmartValidator) validator).validate(getTarget(), getBindingResult(), validationHints);
            }
            else if (validator != null) {
                validator.validate(getTarget(), getBindingResult());
            }
        }
    }

    // 转换堆栈如下:
    "http-nio-8080-exec-6@2418" daemon prio=5 tid=0x25 nid=NA runnable
  java.lang.Thread.State: RUNNABLE
      at org.springframework.core.convert.support.GenericConversionService.handleResult(GenericConversionService.java:328)
      at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:204)
      at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:173)
      at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:108)
      at org.springframework.beans.TypeConverterSupport.doConvert(TypeConverterSupport.java:64)
      at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:47)
      at org.springframework.validation.DataBinder.convertIfNecessary(DataBinder.java:713)
      at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:126)
      at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
      at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158)
      at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128)
      at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
      at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
      at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
      at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
      at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
      at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
      at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
      at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
      at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
      at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
      at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
      at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
      at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
      at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
      at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
      at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
      at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
      at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650)
      at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
      at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
      at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
      at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
      at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
      at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
      at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
      - locked <0x2028> (a org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper)
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
      at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
      at java.lang.Thread.run(Thread.java:745)

View Code
    /**
     * Convert the value to the required type (if necessary from a String),
     * for the specified property.
     * @param propertyName name of the property
     * @param oldValue the previous value, if available (may be {@code null})
     * @param newValue the proposed new value
     * @param requiredType the type we must convert to
     * (or {@code null} if not known, for example in case of a collection element)
     * @param typeDescriptor the descriptor for the target property or field
     * @return the new value, possibly the result of type conversion
     * @throws IllegalArgumentException if type conversion failed
     */
    @SuppressWarnings("unchecked")
    public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue,
            Class<T> requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException {

        // Custom editor for this type?
        PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);

        ConversionFailedException conversionAttemptEx = null;

        // No custom editor but custom ConversionService specified?
        ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
        if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
            TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
            if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
                try {
                    return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
                }
                catch (ConversionFailedException ex) {
                    // fallback to default conversion logic below
                    conversionAttemptEx = ex;
                }
            }
        }

        Object convertedValue = newValue;

        // Value not of required type?
        if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
            if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
                    convertedValue instanceof String) {
                TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
                if (elementTypeDesc != null) {
                    Class<?> elementType = elementTypeDesc.getType();
                    if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
                        convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
                    }
                }
            }
            if (editor == null) {
                editor = findDefaultEditor(requiredType);
            }
            convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
        }

        boolean standardConversion = false;

        if (requiredType != null) {
            // Try to apply some standard type conversion rules if appropriate.

            if (convertedValue != null) {
                if (Object.class == requiredType) {
                    return (T) convertedValue;
                }
                else if (requiredType.isArray()) {
                    // Array required -> apply appropriate conversion of elements.
                    if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
                        convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
                    }
                    return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
                }
                else if (convertedValue instanceof Collection) {
                    // Convert elements to target type, if determined.
                    convertedValue = convertToTypedCollection(
                            (Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor);
                    standardConversion = true;
                }
                else if (convertedValue instanceof Map) {
                    // Convert keys and values to respective target type, if determined.
                    convertedValue = convertToTypedMap(
                            (Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor);
                    standardConversion = true;
                }
                if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
                    convertedValue = Array.get(convertedValue, 0);
                    standardConversion = true;
                }
                if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
                    // We can stringify any primitive value...
                    return (T) convertedValue.toString();
                }
                else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
                    if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {
                        try {
                            Constructor<T> strCtor = requiredType.getConstructor(String.class);
                            return BeanUtils.instantiateClass(strCtor, convertedValue);
                        }
                        catch (NoSuchMethodException ex) {
                            // proceed with field lookup
                            if (logger.isTraceEnabled()) {
                                logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex);
                            }
                        }
                        catch (Exception ex) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", ex);
                            }
                        }
                    }
                    String trimmedValue = ((String) convertedValue).trim();
                    if (requiredType.isEnum() && "".equals(trimmedValue)) {
                        // It's an empty enum identifier: reset the enum value to null.
                        return null;
                    }
                    convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
                    standardConversion = true;
                }
                else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {
                    convertedValue = NumberUtils.convertNumberToTargetClass(
                            (Number) convertedValue, (Class<Number>) requiredType);
                    standardConversion = true;
                }
            }
            else {
                // convertedValue == null
                if (javaUtilOptionalEmpty != null && requiredType == javaUtilOptionalEmpty.getClass()) {
                    convertedValue = javaUtilOptionalEmpty;
                }
            }

            if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
                if (conversionAttemptEx != null) {
                    // Original exception from former ConversionService call above...
                    throw conversionAttemptEx;
                }
                else if (conversionService != null) {
                    // ConversionService not tried before, probably custom editor found
                    // but editor couldn't produce the required type...
                    TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
                    if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
                        return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
                    }
                }

                // Definitely doesn't match: throw IllegalArgumentException/IllegalStateException
                StringBuilder msg = new StringBuilder();
                msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue));
                msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'");
                if (propertyName != null) {
                    msg.append(" for property '").append(propertyName).append("'");
                }
                if (editor != null) {
                    msg.append(": PropertyEditor [").append(editor.getClass().getName()).append(
                            "] returned inappropriate value of type '").append(
                            ClassUtils.getDescriptiveType(convertedValue)).append("'");
                    throw new IllegalArgumentException(msg.toString());
                }
                else {
                    msg.append(": no matching editors or conversion strategy found");
                    throw new IllegalStateException(msg.toString());
                }
            }
        }

        if (conversionAttemptEx != null) {
            if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) {
                throw conversionAttemptEx;
            }
            logger.debug("Original ConversionService attempt failed - ignored since " +
                    "PropertyEditor based conversion eventually succeeded", conversionAttemptEx);
        }

        return (T) convertedValue;
    }

以上是 @ModelAttribute 的参数解析, 下面我们再来看两个常用的类型解析:

1. @RequestParam 解析, 解析普通的变量;

2. @RequestBody 解析, 解析 json 的请求;

@RequestParam 的参数解析, 从 HandlerMethodArgumentResolverComposite -> RequestParamMethodArgumentResolver :

    
    // org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
    // org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver
    @Override
    public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        // 使用 NamedValueInfo 来封装参数, 
        NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
        MethodParameter nestedParameter = parameter.nestedIfOptional();

        // 变量名称替换 比如将 ${a} 替换为 a; 这里会经过一层 BeanExpressionResolver 的解析处理
        Object resolvedName = resolveStringValue(namedValueInfo.name);
        if (resolvedName == null) {
            throw new IllegalArgumentException(
                    "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
        }

        // 根据参数名, 获取该值,该方法为 子类的扩展点,即由 RequestParamMethodArgumentResolver 处理
        Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
        if (arg == null) {
            if (namedValueInfo.defaultValue != null) {
                arg = resolveStringValue(namedValueInfo.defaultValue);
            }
            else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
            }
            arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
        }
        else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = resolveStringValue(namedValueInfo.defaultValue);
        }

        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
            try {
                // 进行类型转换切入点处理, 由于前面返回的参数是 String 类型的,所以,类型的转换必然交给此处处理
                arg = binder.convertIfNecessary(arg, parameter.getParameterType(), 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;
    }
    
    /**
     * Obtain the named value for the given method parameter.
     */
    private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
        NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
        if (namedValueInfo == null) {
            namedValueInfo = createNamedValueInfo(parameter);
            namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
            this.namedValueInfoCache.put(parameter, namedValueInfo);
        }
        return namedValueInfo;
    }

    // RequestParamMethodArgumentResolver, 进行创建 NamedValueInfo
    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
        RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
        return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
    }

    /**
     * Resolve the given annotation-specified value,
     * potentially containing placeholders and expressions.
     */
    private Object resolveStringValue(String value) {
        if (this.configurableBeanFactory == null) {
            return value;
        }
        String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
        BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
        if (exprResolver == null) {
            return value;
        }
        return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
    }

    // RequestParamMethodArgumentResolver
    @Override
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
        HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
        MultipartHttpServletRequest multipartRequest =
                WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);

        Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
        if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
            return mpArg;
        }

        Object 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) {
            // 此处允许传入数组长度的为n的参数,但是只会取第一个参数使用, 而且返回值都是 String 类型 
            String[] paramValues = request.getParameterValues(name);
            if (paramValues != null) {
                arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
            }
        }
        return arg;
    }

总结下 @RequestParam 的处理过程:

1. 由于 RequestParam 的处理方式简单,直接继承父类操作 resolveArgument();

2. 使用 namedValueInfo 来封装请求参数, 一般只针对简单类型的参数操作;

3. 解析定义的 变量名称,可支持 ${xxx} 这样的 expr 表达式处理;

4. 根据变量名称,从request中获取参数,返回第一个值作为参数的原始值;

5. 经过一些类型转换操作,如果需要的话;

6. 值设置完成后,预留一个扩展点给用户;

其实,简单说 @RequestParam 的处理就是,直接 获取 request 中对应的变量值即可;

下面,我们来看一个复杂点的参数解析: @RequestBody 的解析;

起点当然还是 HandlerMethodArgumentResolverComposite.resolveArgument() 了;

    
    // org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
    /**
     * Throws MethodArgumentNotValidException if validation fails.
     * @throws HttpMessageNotReadableException if {@link RequestBody#required()}
     * is {@code true} and there is no body content or if there is no suitable
     * converter to read the content with.
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        // 先检查可选选循环嵌套数情况
        parameter = parameter.nestedIfOptional();
        // 使用消息转换器进行接收参数,因为 对json的解析,不像其他参数解析一样套路只有一个,这个是有大量数据的解析,应该把这个选择权交给用户
        // 比如默认使用 jackson 来解析, 但是往往其效率并不高,而用户则可以选择像 fastjson 这样的组件来处理
        Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
        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 adaptArgumentIfNecessary(arg, parameter);
    }

    @Override
    protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
            Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);

        // 调用父类解析方法
        Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
        if (arg == null) {
            if (checkRequired(parameter)) {
                throw new HttpMessageNotReadableException("Required request body is missing: " +
                        parameter.getMethod().toGenericString());
            }
        }
        return arg;
    }
    
    // org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver
    /**
     * Create the method argument value of the expected parameter type by reading
     * from the given HttpInputMessage.
     * @param <T> the expected type of the argument value to be created
     * @param inputMessage the HTTP input message representing the current request
     * @param parameter the method parameter descriptor (may be {@code null})
     * @param targetType the target type, not necessarily the same as the method
     * parameter type, e.g. for {@code HttpEntity<String>}.
     * @return the created method argument value
     * @throws IOException if the reading from the request fails
     * @throws HttpMediaTypeNotSupportedException if no suitable message converter is found
     */
    @SuppressWarnings("unchecked")
    protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
            Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

        // Content-Type: application/json
        MediaType contentType;
        boolean noContentType = false;
        try {
            contentType = inputMessage.getHeaders().getContentType();
        }
        catch (InvalidMediaTypeException ex) {
            throw new HttpMediaTypeNotSupportedException(ex.getMessage());
        }
        if (contentType == null) {
            noContentType = true;
            contentType = MediaType.APPLICATION_OCTET_STREAM;
        }

        Class<?> contextClass = (parameter != null ? parameter.getContainingClass() : null);
        Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
        if (targetClass == null) {
            ResolvableType resolvableType = (parameter != null ?
                    ResolvableType.forMethodParameter(parameter) : ResolvableType.forType(targetType));
            targetClass = (Class<T>) resolvableType.resolve();
        }

        HttpMethod httpMethod = ((HttpRequest) inputMessage).getMethod();
        Object body = NO_VALUE;

        try {
            // 通过获取 inputMessage.getBody(); 并封装为 EmptyBodyCheckingHttpInputMessage
            inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage);

            // 循环调用 消息转换器, 依次处理
            for (HttpMessageConverter<?> converter : this.messageConverters) {
                Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
                if (converter instanceof GenericHttpMessageConverter) {
                    // fastjson 走这里
                    GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
                    if (genericConverter.canRead(targetType, contextClass, contentType)) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
                        }
                        if (inputMessage.getBody() != null) {
                            inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
                            body = genericConverter.read(targetType, contextClass, inputMessage);
                            body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
                        }
                        else {
                            body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
                        }
                        break;
                    }
                }
                else if (targetClass != null) {
                    // 调用转换器的 canRead() 方法,判断是否可处理该类型的数据,由这郸城 接入 json 转换器
                    // 当然,这里在公共抽象类中会有一个封装, return supports(clazz) && canRead(mediaType); 可以重写这两个方法
                    // 在springmvc中,不定义 json 转换器将导致报错
                    if (converter.canRead(targetClass, contentType)) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
                        }
                        // 做消息转换的切点埋入
                        if (inputMessage.getBody() != null) {
                            inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
                            body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
                            body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
                        }
                        else {
                            body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
                        }
                        break;
                    }
                }
            }
        }
        catch (IOException ex) {
            throw new HttpMessageNotReadableException("I/O error while reading input message", ex);
        }

        if (body == NO_VALUE) {
            if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
                    (noContentType && inputMessage.getBody() == null)) {
                return null;
            }
            throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
        }

        return body;
    }
    
    // 内部类 EmptyBodyCheckingHttpInputMessage 如下
    private static class EmptyBodyCheckingHttpInputMessage implements HttpInputMessage {

        private final HttpHeaders headers;

        private final InputStream body;

        private final HttpMethod method;


        public EmptyBodyCheckingHttpInputMessage(HttpInputMessage inputMessage) throws IOException {
            this.headers = inputMessage.getHeaders();
            InputStream inputStream = inputMessage.getBody();
            if (inputStream == null) {
                this.body = null;
            }
            else if (inputStream.markSupported()) {
                inputStream.mark(1);
                this.body = (inputStream.read() != -1 ? inputStream : null);
                inputStream.reset();
            }
            else {
                // 检测是否有数据,没有则设置为 null
                PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
                int b = pushbackInputStream.read();
                if (b == -1) {
                    this.body = null;
                }
                else {
                    this.body = pushbackInputStream;
                    pushbackInputStream.unread(b);
                }
            }
            this.method = ((HttpRequest) inputMessage).getMethod();
        }

        @Override
        public HttpHeaders getHeaders() {
            return this.headers;
        }

        @Override
        public InputStream getBody() throws IOException {
            return this.body;
        }

        public HttpMethod getMethod() {
            return this.method;
        }
    }

这里我们使用 fastjson 的转换器来看一下json的转换吧; FastJsonHttpMessageConverter.read()

由于 FastJsonHttpMessageConverter 是一个 GenericHttpMessageConverter, 所以会走第一个通用逻辑.

    
    // com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter
    public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
        return super.canRead(contextClass, mediaType);
    }
    @Override
    protected boolean supports(Class<?> clazz) {
        // 交由 canRead() 统一控制
        return true;
    }
    /**
     * Returns {@code true} if any of the {@linkplain #setSupportedMediaTypes(List)
     * supported} media types {@link MediaType#includes(MediaType) include} the
     * given media type.
     * @param mediaType the media type to read, can be {@code null} if not specified.
     * Typically the value of a {@code Content-Type} header.
     * @return {@code true} if the supported media types include the media type,
     * or if the media type is {@code null}
     */
    protected boolean canRead(MediaType mediaType) {
        // 没有 application/json 的设置也是支持的哦
        if (mediaType == null) {
            return true;
        }
        for (MediaType supportedMediaType : getSupportedMediaTypes()) {
            if (supportedMediaType.includes(mediaType)) {
                return true;
            }
        }
        return false;
    }

    // 而其 read() 方法,则是比较简洁的,直接调用 fastjson 的 parseObject() 方法即可;
    /*
     * @see org.springframework.http.converter.GenericHttpMessageConverter#read(java.lang.reflect.Type, java.lang.Class, org.springframework.http.HttpInputMessage)
     */
    public Object read(Type type, //
                       Class<?> contextClass, //
                       HttpInputMessage inputMessage //
    ) throws IOException, HttpMessageNotReadableException {

        InputStream in = inputMessage.getBody();
        return JSON.parseObject(in, fastJsonConfig.getCharset(), type, fastJsonConfig.getFeatures());
    }

好了,我们来总结下 json 的转换方式吧!

1. 检查循环嵌套问题;

2. 将消息体封装进 EmptyBodyCheckingHttpInputMessage 中;

3. 获取配置好的转换器,其中必须要有 json 转换器,否则将报错;

4. 调用配置json转换器,进行格式验证;

5. 进行消息转换,转换逻辑与spring就没什么关系了;(如果有兴趣查看json的转换逻辑可以参考fastjson源码)

这里一个关键的步骤是,配置一个json转换器! 这里给出一个最简单的配置方式:

    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean id="fastJsonHttpMessageConverter" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>application/json;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

仅此足够!


猜你喜欢

  • 22
    • www.throwable.club 1年前
    • 快照

    SpringMVC请求参数接收总结(一)

    在日常使用 SpringMVC 进行开发的时候,有可能遇到前端各种类型的请求参数,这里做一次相对全面的总结。 SpringMVC 中处理控制器参数的接口是 HandlerMethodArgumentResolver ,此接口有众多子类...

  • 1

    SpringMVC,SpringBoot快速验证参数是否完整 基于@Valid注解 07 五月 2019 7:36:37 晚上在接口开发中,经常是需要对传入参数完整性进行验证的,或者对手机号格式进行验证等,而一般不了解@Valid注解之前,都是通过自己封装方法来验证,都是一堆if...

  • 14

    本篇文章讲述响应的处理,关于请求的处理可以看我第一篇文章 链接地址: https://segmentfault.com/a/11... 前期准备 使用环境

  • 18

    DispatcherServlet的初始化 DispatcherServlet的刷新 初始化MultipartResolver 初始化HandlerAdapters 初始化HandlerExceptionResolvers 初始化RequestToVi...

  • 0

    简介 SpringMVC可以说的上是当前最优秀的MVC框架,采用了松散耦合可插拔组件结构,比其他MVC框架更具扩展性和灵活性;为了提高框架的扩展性和灵活性,设计了松耦合可插拔的组件。理解SpringMVC的原理,在面试或工作中都十分的重...

  • 47

    通过Feign包装rpc的调用姿势,在使用的版本中发现一个奇怪的bug,大部分场景下请求正常,少数情况下请求返回400,记录下原因 场景复现 1. 环境相关版本 Spring版本如 <...

  • 32

    内网通过K8S搭建多个分支测试环境,可是如果外网需要访问而且域名都是一致的情况下,这个时候变得麻烦了。如何通过不同的请求参数访问不同的后端环境呢,答案是可以的,通过lua可以达到。入口:http://fengwan.blog.51cto.com/?envs=branches#branches环境http://fe...

  • 9

    If you need performance and good productivity, you will love Gin. 这是 Gin 源码学习的第一篇,为什么是 Gin 呢? 正如 Gin 官方文档中所说,Gin 是一个注重性能和生产的 web 框架,并且号称其性能要比 httpr...

  • 0

    使用 POST 方式请求 JSON 数据到服务器 WebAPI 接口时需要将 JSON 格式封装成数据模型接收参数。即使参数较少,每个接口仍然需要单独创建模型接收。下面方法实现了将 JSON 参数中的字段作为接口参数接收。实现的并不完美,现在只支持 JSON...

  • 41

    Parsing MXNet Parameter File In C++ This small project aims to extract parameters from pretrained MXNet .params file in C++, and only depends on the dmlc-core...

关于极客头条


聚合每日国内外有价值,有趣的链接。

AD