zoukankan      html  css  js  c++  java
  • SpringMVC解析文件参数过程

      我们知道SpringMVC 接收文件的时候直接用一个MultipartFile 接收即可,但是SpringMVC是如何解析以及如何绑定到参数的不清楚。

    1. SpringMVC接收文件的接口如下

        @RequestMapping("/upload")
        @ResponseBody
        public Map<String, Object> uploadPicture(MultipartFile imgFile) {
            Map<String, Object> result = new HashMap<String, Object>();
            result.put("error", 1);
    
            if (imgFile == null) {
                result.put("message", "文件没接到");
                return result;
            }
            logger.debug("file -> {},viewId ->{}", imgFile.getOriginalFilename());
    
            String fileOriName = imgFile.getOriginalFilename();// 获取原名称
            String fileNowName = UUIDUtils.getUUID2() + "." + FilenameUtils.getExtension(fileOriName);// 生成唯一的名字
            try {
                FileHandleUtil.uploadSpringMVCFile(imgFile, fileNowName);
    
            } catch (Exception e) {
                logger.error("uploadPicture error", e);
                return result;
            }
    
            String id = UUIDUtils.getUUID();
            Document document = new Document();
            document.setCreatetime(new Date());
            document.setPath(fileNowName);
            document.setId(id);
            document.setOriginName(fileOriName);
            document.setUploaderUsername(MySystemUtils.getLoginUsername());
    
            documentService.insert(document);
    
            // 回传JSON结果
            result.put("error", 0);
            result.put("url", "/document/getDocument/" + id);
            return result;
        }

      我们用类似的接口即可实现文件上传。接下来研究SpringMVC 是如何绑定参数的。

    2. 绑定参数解读

    SpringMVC 处理入口: org.springframework.web.servlet.DispatcherServlet#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);
                    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()) {
                            logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                        }
                        if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
    
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
    
                    // Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
    
                    applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                }
                catch (Exception ex) {
                    dispatchException = ex;
                }
                catch (Throwable err) {
                    // As of 4.3, we're processing Errors thrown from handler methods as well,
                    // making them available for @ExceptionHandler methods and other scenarios.
                    dispatchException = new NestedServletException("Handler dispatch failed", err);
                }
                processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
            }
            catch (Exception ex) {
                triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
            }
            catch (Throwable err) {
                triggerAfterCompletion(processedRequest, response, mappedHandler,
                        new NestedServletException("Handler processing failed", err));
            }
            finally {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    // Instead of postHandle and afterCompletion
                    if (mappedHandler != null) {
                        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                    }
                }
                else {
                    // Clean up any resources used by a multipart request.
                    if (multipartRequestParsed) {
                        cleanupMultipart(processedRequest);
                    }
                }
            }
        }

    1. org.springframework.web.servlet.DispatcherServlet#checkMultipart  检查是否是上传文件的接口,如果是上传文件的接口就重置processedRequest 的引用

        protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
            if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
                if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
                    logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
                            "this typically results from an additional MultipartFilter in web.xml");
                }
                else if (hasMultipartException(request) ) {
                    logger.debug("Multipart resolution failed for current request before - " +
                            "skipping re-resolution for undisturbed error rendering");
                }
                else {
                    try {
                        return this.multipartResolver.resolveMultipart(request);
                    }
                    catch (MultipartException ex) {
                        if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
                            logger.debug("Multipart resolution failed for error dispatch", ex);
                            // Keep processing error dispatch with regular request handle below
                        }
                        else {
                            throw ex;
                        }
                    }
                }
            }
            // If not returned before: return original request.
            return request;
        }

    1. multipartResolver 是 org.springframework.web.multipart.support.StandardServletMultipartResolver。org.springframework.web.multipart.support.StandardServletMultipartResolver#isMultipart: 判断是否是文件上传的请求。

        public boolean isMultipart(HttpServletRequest request) {
            // Same check as in Commons FileUpload...
            if (!"post".equals(request.getMethod().toLowerCase())) {
                return false;
            }
            String contentType = request.getContentType();
            return (contentType != null && contentType.toLowerCase().startsWith("multipart/"));
        }

    2. 最后调用org.springframework.web.multipart.support.StandardServletMultipartResolver#resolveMultipart 解析Multipart

        public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
            return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
        }

    可以看出是创建了一个StandardMultipartHttpServletRequest 对象并且返回去替换掉原来request的引用。org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#StandardMultipartHttpServletRequest(javax.servlet.http.HttpServletRequest, boolean) 构造如下

        public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException {
            super(request);
            if (!lazyParsing) {
                parseRequest(request);
            }
        }

    然后调用: org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#parseRequest

        private void parseRequest(HttpServletRequest request) {
            try {
                // 拿请求接收的文件, 也就是tomcat 临时接收到的文件。
                Collection<Part> parts = request.getParts();
                this.multipartParameterNames = new LinkedHashSet<String>(parts.size());
                MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<String, MultipartFile>(parts.size());
                for (Part part : parts) {
                    // 拿到的信息样式: form-data; name="imgFile"; filename="RegexUtils.java"    
                    String disposition = part.getHeader(CONTENT_DISPOSITION);
                    String filename = extractFilename(disposition);
                    if (filename == null) {
                        filename = extractFilenameWithCharset(disposition);
                    }
                    if (filename != null) {
                        files.add(part.getName(), new StandardMultipartFile(part, filename));
                    }
                    else {
                        this.multipartParameterNames.add(part.getName());
                    }
                }
                setMultipartFiles(files);
            }
            catch (Throwable ex) {
                throw new MultipartException("Could not parse multipart servlet request", ex);
            }
        }

      (1) 从request.getParts() 获取tomcat 接收到的临时文件

      (2) files.add(part.getName(), new StandardMultipartFile(part, filename)); 创建一个StandardMultipartFile 

      (3) org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest#setMultipartFiles 缓存起来。方法如下:

        protected final void setMultipartFiles(MultiValueMap<String, MultipartFile> multipartFiles) {
            this.multipartFiles =
                    new LinkedMultiValueMap<String, MultipartFile>(Collections.unmodifiableMap(multipartFiles));
        }

    经过上面过程之后的processedRequest 如下:(对上传文件的请求提取了文件信息并且记录起来)

     2. ha.handle(processedRequest, response, mappedHandler.getHandler()); 处理以及装配文件参数到controller

    1. 会调用到:org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest

        public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
                Object... providedArgs) throws Exception {
    
            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;
        }

    2. org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues 解析参数

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

    3. 最后调用到org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#resolveArgument 解析参数

        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    
            HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
            if (resolver == null) {
                throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
            }
            return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
        }

    (1) 首先调用 org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#getArgumentResolver 获取参数解析器

        private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
            HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
            if (result == null) {
                for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
                                parameter.getGenericParameterType() + "]");
                    }
                    if (methodArgumentResolver.supportsParameter(parameter)) {
                        result = methodArgumentResolver;
                        this.argumentResolverCache.put(parameter, result);
                        break;
                    }
                }
            }
            return result;
        }

      这里获取到的参数解析器是: org.springframework.web.method.annotation.RequestParamMethodArgumentResolver

    (2) 然后调用org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#resolveArgument 解析参数

        public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    
            NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
            MethodParameter nestedParameter = parameter.nestedIfOptional();
    
            Object resolvedName = resolveStringValue(namedValueInfo.name);
            if (resolvedName == null) {
                throw new IllegalArgumentException(
                        "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
            }
    
            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 {
                    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;
        }

    1》  resolveStringValue(namedValueInfo.name); 获取到参数的名称, 这里获取到的值是:  imgFile

    2》 调用org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#resolveName 获取参数的值

        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) {
                String[] paramValues = request.getParameterValues(name);
                if (paramValues != null) {
                    arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
                }
            }
            return arg;
        }

    上面的代码会走return mpArg; 然后结束方法。所以解析的过程是在:org.springframework.web.multipart.support.MultipartResolutionDelegate#resolveMultipartArgument

        public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)
                throws Exception {
    
            MultipartHttpServletRequest multipartRequest =
                    WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
            boolean isMultipart = (multipartRequest != null || isMultipartContent(request));
    
            if (MultipartFile.class == parameter.getNestedParameterType()) {
                if (multipartRequest == null && isMultipart) {
                    multipartRequest = adaptToMultipartHttpServletRequest(request);
                }
                return (multipartRequest != null ? multipartRequest.getFile(name) : null);
            }
            else if (isMultipartFileCollection(parameter)) {
                if (multipartRequest == null && isMultipart) {
                    multipartRequest = adaptToMultipartHttpServletRequest(request);
                }
                return (multipartRequest != null ? multipartRequest.getFiles(name) : null);
            }
            else if (isMultipartFileArray(parameter)) {
                if (multipartRequest == null && isMultipart) {
                    multipartRequest = adaptToMultipartHttpServletRequest(request);
                }
                if (multipartRequest != null) {
                    List<MultipartFile> multipartFiles = multipartRequest.getFiles(name);
                    return multipartFiles.toArray(new MultipartFile[multipartFiles.size()]);
                }
                else {
                    return null;
                }
            }
            else if (servletPartClass != null) {
                if (servletPartClass == parameter.getNestedParameterType()) {
                    return (isMultipart ? RequestPartResolver.resolvePart(request, name) : null);
                }
                else if (isPartCollection(parameter)) {
                    return (isMultipart ? RequestPartResolver.resolvePartList(request, name) : null);
                }
                else if (isPartArray(parameter)) {
                    return (isMultipart ? RequestPartResolver.resolvePartArray(request, name) : null);
                }
            }
            return UNRESOLVABLE;
        }

    上面代码会走 multipartRequest.getFile(name) 获取MultipartFile, 最后交给:org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest#getFile  (实际上也就是拿取上面checkMultipart 过程中最后一步缓存起来的信息。获取到的实际类型是org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.StandardMultipartFile)

        public MultipartFile getFile(String name) {
            return getMultipartFiles().getFirst(name);
        }
    
        protected MultiValueMap<String, MultipartFile> getMultipartFiles() {
            if (this.multipartFiles == null) {
                initializeMultipart();
            }
            return this.multipartFiles;
        }

    最后映射到参数上的MultipartFile 如下:

    3. multipartFile.transferTo(new File(fileDir + fileName));// 保存文件 

    实际走的方法是:org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.StandardMultipartFile#transferTo

            public void transferTo(File dest) throws IOException, IllegalStateException {
                this.part.write(dest.getPath());
            }

    接着调用: org.apache.catalina.core.ApplicationPart#write

        public void write(String fileName) throws IOException {
            File file = new File(fileName);
            if (!file.isAbsolute()) {
                file = new File(location, fileName);
            }
            try {
                fileItem.write(file);
            } catch (Exception e) {
                throw new IOException(e);
            }
        }

    接着调用: org.apache.tomcat.util.http.fileupload.disk.DiskFileItem#write

        public void write(File file) throws Exception {
            if (isInMemory()) {
                FileOutputStream fout = null;
                try {
                    fout = new FileOutputStream(file);
                    fout.write(get());
                    fout.close();
                } finally {
                    IOUtils.closeQuietly(fout);
                }
            } else {
                File outputFile = getStoreLocation();
                if (outputFile != null) {
                    // Save the length of the file
                    size = outputFile.length();
                    /*
                     * The uploaded file is being stored on disk
                     * in a temporary location so move it to the
                     * desired file.
                     */
                    if (!outputFile.renameTo(file)) {
                        BufferedInputStream in = null;
                        BufferedOutputStream out = null;
                        try {
                            in = new BufferedInputStream(
                                new FileInputStream(outputFile));
                            out = new BufferedOutputStream(
                                    new FileOutputStream(file));
                            IOUtils.copy(in, out);
                            out.close();
                        } finally {
                            IOUtils.closeQuietly(in);
                            IOUtils.closeQuietly(out);
                        }
                    }
                } else {
                    /*
                     * For whatever reason we cannot write the
                     * file to disk.
                     */
                    throw new FileUploadException(
                        "Cannot write uploaded file to disk!");
                }
            }
        }

      getStoreLocation() 获取到文件, 然后进行拷贝操作,拷贝到指定的目录。

    【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
  • 相关阅读:
    Angular9 cdk-virtual-scroll-viewport' is not a known element 报错解决方案
    angular8打包时提示ERROR in Child compilation failed:解决方案
    angular8配置proxy本地跨域代理
    Vue-cli 本地跨域配置方法
    axios中url参数变量配置
    在vue-cli中使用axios时报错TypeError: Cannot set property 'lists' of undefined at eval
    nuxt.js element-ui踩坑记录(已解决)
    nuxt 关闭ESlint 语法检测
    javaScript学习之正则表达式初探
    直接在低版本IE6/7/8浏览器中使用HTML5的audio和video标签播放视频音频的办法
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/14810215.html
Copyright © 2011-2022 走看看