Why can @ RequestParam get file upload data in SpringMVC?

when uploading springmvc files, this method is usually used to obtain the uploaded file data

//descfileid
    @RequestMapping(value="/testUpload",method=RequestMethod.POST)        
    public String testUpload(@RequestParam(value="desc",required=false) String desc,@RequestParam("file") MultipartFile multipartFile) throws IOException{  

    System.out.println("desc : "+desc);
    System.out.println("OriginalFilename : "+multipartFile.getOriginalFilename());
    
    InputStream inputStream = multipartFile.getInputStream();
    System.out.println("inputStream.available() : "+inputStream.available());
    System.out.println("inputStream : "+inputStream);
    
    return "success"; 
}

but as far as I know,
@ RequestParam maps request parameters to the formal parameters of the request processing method.
is often used to handle simple type bindings to handle content encoded by Content-Type: for application/x-www-form-urlencoded.
but the Content-Type: is multipart/form-data, when the file is uploaded, so why can @ RequestParam get the file data at this time? my personal understanding is the reason for MultipartFile. It may be because of the role of springmvc"s file upload parser, but I still don"t understand how to do it.
can you think @ RequestParam can also handle multipart/form-data?

Mar.28,2021

if you look at the source code of spring, you will find that spring parses Content-Type to MediaType, during parameter processing and then handles it by a specific RequestBodyAdviceAdapter registered in the spring framework, and different Content-Type corresponds to different RequestBodyAdviceAdapter. This implementation class implements the parsing of uploaded files.


when spring mvc uploads files, you need to configure the org.springframework.web.multipart.commons.CommonsMultipartResolver file upload parser in the spring mvc configuration file.

1: when a request comes, the doDispatch (HttpServletRequest, HttpServletResponse) method in the DispatcherServlet class is called.

doDispatch method body

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest, false);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        String requestUri = urlPathHelper.getRequestUri(request);
                        logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                try {
                    // Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                }
                finally {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                }

                applyDefaultViewName(request, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Error err) {
            triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                return;
            }
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }

2: call the checkMultipart (request) method in the body of the doDispatch method to determine whether it is a request for file upload, and determine whether it is based on Content-type. If it is a request for file upload, the checkMultipart (request) method will call the method in CommonsMultipartResolver to parse the file (actually calling the long-distance file upload plug-in of apache commons-fileupload). After the parsing is completed, the file has already been uploaded to the local disk of the server. Please see the following code snippet.

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
        if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
            if (request instanceof MultipartHttpServletRequest) {
                logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
                        "this typically results from an additional MultipartFilter in web.xml");
            }
            else {
                return this.multipartResolver.resolveMultipart(request);
            }
        }
        // If not returned before: return original request.
        return request;
    }

the resolveMultipart method of the CommonsMultipartResolver class is called in the body of the checkMultipart method to parse the file, which returns a MultipartHttpServletRequest object that contains the file object local to the server that has been uploaded.

3: get the method of the specific processor (mv = ha.handle (processedRequest, response, mappedHandler.getHandler ()) line of code).
if the file is uploaded, processedRequest is the object returned by the checkMultipart method above.

the handle method of the org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter class that is actually called, which gets the annotations on the actual called method parameters, and then gets the corresponding values from processedRequest according to some configuration of the annotations.

for example, the testUpload () method above you. In the handle () method of AnnotationMethodHandlerAdapter, you will get the annotation @ RequestParam as the method parameter of testUpload (), then parse the annotation, get the value of desc from the processedRequest request, get the file named file, and finally call the testUploaad () method. You can take a look at the org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveHandlerArguments (Method, Object, NativeWebRequest, ExtendedModelMap) method body for details on how to analyze it.

Menu