鍍金池/ 問答/Java  HTML/ 在SpringMVC中@RequestParam為什么可以獲得文件上傳的數(shù)據(jù)?

在SpringMVC中@RequestParam為什么可以獲得文件上傳的數(shù)據(jù)?

在做springmvc文件上傳的時候通常都會用這種方法獲得上傳的文件數(shù)據(jù)

//desc,file是頁面上標簽的id屬性
    @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"; 
}

但是就我所知
@RequestParam是將請求參數(shù)映射到請求處理方法的形參中
常用來處理簡單類型的綁定,用來處理Content-Type: 為?application/x-www-form-urlencoded編碼的內容。
可是文件上傳的時候Content-Type:為multipart/form-data,那么為什么此時@RequestParam可以得到文件數(shù)據(jù),我個人的理解是MultipartFile 的原因,可能是因為springmvc的文件上傳解析器起的作用,但是我還是沒理解怎么做到的
可以認為@RequestParam也可以處理multipart/form-data嗎。

回答
編輯回答
不討囍

spring mvc上傳文件的時候,你需要在spring mvc配置文件中配置org.springframework.web.multipart.commons.CommonsMultipartResolver文件上傳解析器。

1:當一個請求過來的時候,會調用DispatcherServlet類中的doDispatch(HttpServletRequest, HttpServletResponse)方法。

doDispatch方法體

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:在doDispatch方法體中調用checkMultipart(request)方法判斷是否是文件上傳的請求,判斷是根據(jù)Content-type來的,如果是文件上傳的請求,在checkMultipart(request)方法中就會調用在CommonsMultipartResolver中的方法進行文件解析(實際是調用apache commons-fileupload的文件長傳插件),解析完成后,其實文件已經上傳到服務器本地磁盤了,請看下面的代碼片段。

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

在checkMultipart方法體中調用了CommonsMultipartResolver類的resolveMultipart方法去解析文件,該方法返回的是MultipartHttpServletRequest對象,該對象中存有已經上傳的服務器本地的文件對象。

3:獲取具體處理器的方法(mv = ha.handle(processedRequest, response, mappedHandler.getHandler()) 這行代碼)。
如果是文件上傳的話,processedRequest是上面的checkMultipart方法返回的對象。

實際調用的org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter類的handle方法,該方法中獲取實際被調用的方法參數(shù)上有哪些注解,然后根據(jù)注解的一些配置,從processedRequest獲取對應的值。

比如你上面的testUpload()方法。在AnnotationMethodHandlerAdapter的handle()的方法中會獲取testUpload()的方法參數(shù)有@RequestParam這個注解,然后解析這個注解,從processedRequest請求中獲取到desc的值,獲取到名為file的文件,最后調用testUploaad()方法。具體是怎么解析的可以看看org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveHandlerArguments(Method, Object, NativeWebRequest, ExtendedModelMap)這個方法體。

2017年12月9日 05:15
編輯回答
不討囍

如果看下spring的源碼會發(fā)現(xiàn),spring在參數(shù)處理的時候會把Content-Type解析為MediaType,然后由spring框架中注冊的具體RequestBodyAdviceAdapter來處理,不同的Content-Type會對應不同的RequestBodyAdviceAdapter。這個實現(xiàn)類實現(xiàn)了對上傳文件的解析。

2017年10月19日 01:57