SpringBoot文件上传的原理解析

 更新时间:2023年10月10日 10:27:42   作者:刘婉晴  
这篇文章主要介绍了SpringBoot文件上传的原理解析,SpringBoot 文件上传是一种方便快捷的方式,可以将文件上传到服务器,通过使用SpringBoot的文件上传功能,可以轻松地实现文件上传功能,需要的朋友可以参考下

一、请求进入,使用文件上传解析器判断并封装

文件上传解析器:只能接收标准的 Servlet 方式上传的文件

@ConditionalOnMissingBean({MultipartResolver.class}) 判断容器中无文件上传解析器,若无自动创建

    @ConditionalOnMissingBean({MultipartResolver.class}) // 判断注解
    public StandardServletMultipartResolver multipartResolver() {
        StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
        multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
        return multipartResolver;
    }

DispatcherServlet.class —— 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 {
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;
                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request; // 判断是否是文件上传请求(封装好的文件上传请求与原请求不相等,则判断是文件上传请求)
                    mappedHandler = this.getHandler(processedRequest); // 找谁能处理文件上传请求
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }
        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }
        }
    }

(1) processedRequest = this.checkMultipart(request) —— 用于判断是否是文件上传请求

Step Into 查看:发现使用 multipartResolver 判断是否是文件上传请求

   protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
        if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { // 判断是否是文件上传请求
            if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
                if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
                    this.logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
                }
            } else if (this.hasMultipartException(request)) {
                this.logger.debug("Multipart resolution previously failed for current request - skipping re-resolution for undisturbed error rendering");
            } else {
                try {
                    return this.multipartResolver.resolveMultipart(request); // 解析文件上传请求
                } catch (MultipartException var3) {
                    if (request.getAttribute("javax.servlet.error.exception") == null) {
                        throw var3;
                    }
                }
                this.logger.debug("Multipart resolution failed for error dispatch", var3);
            }
        }
        return request;
    }

StepInto—— multipartResolver 判断是否是文件上传请求方式

用 String 工具类判断是否以 multipart/ 开头(这也解释了为什么我们在写前端表单接收文件时,必须使用multipart)

    public boolean isMultipart(HttpServletRequest request) {
        return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
    }

(2) this.multipartResolver.resolveMultipart(request) 解析文件上传请求

其将文件上传请求封装为 MultipartHttpServletRequest 类返回

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

二、参数解析器解析请求中的文件内容封装成 MultipartFile

文件请求参数解析器:

在这里插入图片描述

(1) InvocableHandlerMethod.class 找到参数解析器,执行文件上传代理

  public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest servletRequest = (HttpServletRequest)request.getNativeRequest(HttpServletRequest.class);
        Assert.state(servletRequest != null, "No HttpServletRequest");
        RequestPart requestPart = (RequestPart)parameter.getParameterAnnotation(RequestPart.class);
        boolean isRequired = (requestPart == null || requestPart.required()) && !parameter.isOptional();
        String name = this.getPartName(parameter, requestPart);
        parameter = parameter.nestedIfOptional();
        Object arg = null;
        Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); // 文件上传代理
        if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
            arg = mpArg;
        } else {
            try {
                HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
                arg = this.readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
                if (binderFactory != null) {
                    WebDataBinder binder = binderFactory.createBinder(request, arg, name);
                    if (arg != null) {
                        this.validateIfApplicable(binder, parameter);
                        if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
                            throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
                        }
                    }
                    if (mavContainer != null) {
                        mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
                    }
                }
            } catch (MultipartException | MissingServletRequestPartException var13) {
                if (isRequired) {
                    throw var13;
                }
            }
        }
  }

(2)确定每个参数的值

       Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);

(3)解析所有参数

           for(int i = 0; i < parameters.length; ++i) {
                MethodParameter parameter = parameters[i];
                parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
                args[i] = findProvidedArgument(parameter, providedArgs);
                if (args[i] == null) {
                    if (!this.resolvers.supportsParameter(parameter)) {
                        throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
                    }
                    try {
                        args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
                    } catch (Exception var10) {
                        if (logger.isDebugEnabled()) {
                            String exMsg = var10.getMessage();
                            if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                                logger.debug(formatArgumentError(parameter, exMsg));
                            }
                        }
                        throw var10;
                    }
                }
            }

三、将 request 文件封装为一个 Map (Map<String, MultipartFile>)

AbstractMultipartHttpServletRequest.class

    public List<MultipartFile> getFiles(String name) {
        List<MultipartFile> multipartFiles = (List)this.getMultipartFiles().get(name);
        return multipartFiles != null ? multipartFiles : Collections.emptyList();
    }
public Map<String, MultipartFile> getFileMap() {
        return this.getMultipartFiles().toSingleValueMap();
    }

总结:

  • 请求进入,使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
  • 参数解析器解析请求中的文件内容封装成 MultipartFile
  • 将 request 文件封装为一个 Map (Map<String, MultipartFile>)

通过观察源码,可以得到许多 SpringBoot 为我们封装好的文件工具类,如 FileCopyUtils 实现文件流的拷贝

到此这篇关于SpringBoot文件上传的原理解析的文章就介绍到这了,更多相关SpringBoot文件上传内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring Boot项目如何使用Maven打包并带上依赖

    Spring Boot项目如何使用Maven打包并带上依赖

    在这篇博客中,介绍如何使用Maven将Spring Boot项目及其依赖项打包成一个可执行的jar文件。我们将使用Spring Boot的spring-boot-maven-plugin插件来完成这个任务,感兴趣的朋友跟随小编一起看看吧
    2023-06-06
  • springcloud集成nacos 使用lb 无效问题解决方案

    springcloud集成nacos 使用lb 无效问题解决方案

    这篇文章主要介绍了解决springcloud集成nacos 使用lb 无效,通过查看spring-cloud-starter-gateway jar中的自动配置类的源码,得知,该jar包中是不支持负载均衡的,需要引入spring-cloud-starter-loadbalancer 来支持,需要的朋友可以参考下
    2023-04-04
  • java NIO实现简单聊天程序

    java NIO实现简单聊天程序

    这篇文章主要为大家详细介绍了java NIO实现简单聊天程序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • Java发送邮箱验证码、session校验功能

    Java发送邮箱验证码、session校验功能

    本篇主要描述“发送邮箱验证码、session校验”相关前(html\js)后(java)台代码,业务逻辑示例,需要的朋友可以参考下
    2018-02-02
  • SpringBoot声明式事务的简单运用说明

    SpringBoot声明式事务的简单运用说明

    这篇文章主要介绍了SpringBoot声明式事务的简单运用说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • SpringBoot整合Lombok插件与使用详解

    SpringBoot整合Lombok插件与使用详解

    Lombok是Java开发的插件,通过注解自动生成常用代码,如getter/setter,节省开发时间,提高效率,它在编译期生成方法,不影响性能,安装Lombok需要添加Maven依赖和IDEA插件,使用注解如@Data、@Getter等简化代码编写,官网提供详细文档
    2024-09-09
  • java和jsp之间的request传值方法

    java和jsp之间的request传值方法

    这篇文章主要给大家介绍了关于java和jsp之间的request传值方法的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • Gson中@JsonAdater注解的几种方式总结

    Gson中@JsonAdater注解的几种方式总结

    这篇文章主要介绍了Gson中@JsonAdater注解的几种方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • Lucene 索引删除策略源码解析

    Lucene 索引删除策略源码解析

    这篇文章主要为大家介绍了Lucene 索引删除策略源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • 简单分析Java的求值策略原理

    简单分析Java的求值策略原理

    在本篇文章里小编给大家整理的是一篇关于简单分析Java的求值策略原理内容,有需要的朋友们可以学习下。
    2021-06-06

最新评论