Spring MVC文件请求处理-MultipartResolver

Spring Boot中的MultipartResolver是一个用于解析multipart/form-data类型请求的策略接口,通常用于文件上传。
在这里插入图片描述
对应后端使用MultipartFile对象接收。

   @RequestMapping("/upload")public String uploadFile(MultipartFile file) throws IOException {String fileName = file.getOriginalFilename();String filePath = "D:\\";File file1 = new File(filePath+"\\"+fileName);file.transferTo(file1);return "ok";}

一、MultipartResolver接口

MultipartResolver是个接口,不做任何实现。

public interface MultipartResolver {  /*** 判断当前HttpServletRequest请求是否是文件请求*/boolean isMultipart(HttpServletRequest request);  /***  将当前HttpServletRequest请求的数据(文件和普通参数)封装成MultipartHttpServletRequest对象*/MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;  /***  清除文件上传产生的临时资源(如服务器本地临时文件)*/void cleanupMultipart(MultipartHttpServletRequest request);  
}

MultipartResolver在DispatcherServlet使用

我们都知道请求首先到达DispatcherServlet,它负责协调和组织不同组件完成请求处理并返回响应工作,所以DispatcherServlet中持有MultipartResolver成员变量,
在这里插入图片描述
在onRefresh中完成包括MultipartResolver在内的9个组件。
在这里插入图片描述

doDispatch中调用checkMultipart(request); 方法将请求转换为 multipart 请求,如该请求不是multipart 请求则返回原request

	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)) {logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");}}else if (hasMultipartException(request)) {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 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;}

DispatcherServlet处理文件请求会经过以下步骤:

1、判断当前HttpServletRequest请求是否是文件请求

  • 是:将当前HttpServletRequest请求的数据(文件和普通参数)封装成MultipartHttpServletRequest对象
  • 不是:不处理

2、DispatcherServlet对原始HttpServletRequest或MultipartHttpServletRequest对象进行业务处理
3、业务处理完成,清除文件上传产生的临时资源

二、MultipartResolver的实现类

Spring提供了两个MultipartResolver实现类:

  • StandardServletMultipartResolver:根据Servlet 3.0+ Part Api实现(默认使用)
  • CommonsMultipartResolver:根据Apache Commons FileUpload实现,需要引入相关的依赖

三、 StandardServletMultipartResolver

StandardServletMultipartResolver是根据Servlet 3.0+ Part Api实现,也是我们默认使用文件解析器。
在这里插入图片描述

public class StandardServletMultipartResolver implements MultipartResolver {private boolean resolveLazily = false;/*** 设置是否延迟解析* 可通过配置文件进行设置spring.servlet.multipart.resolve-lazily=true* @since 3.2.9*/public void setResolveLazily(boolean resolveLazily) {this.resolveLazily = resolveLazily;}/*** 判断是否是multipart请求*/@Overridepublic boolean isMultipart(HttpServletRequest request) {return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");}/*** 返回StandardMultipartHttpServletRequest请求(主要部分)*/@Overridepublic MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {return new StandardMultipartHttpServletRequest(request, this.resolveLazily);}/*** 清理临时文件*/@Overridepublic void cleanupMultipart(MultipartHttpServletRequest request) {if (!(request instanceof AbstractMultipartHttpServletRequest) ||((AbstractMultipartHttpServletRequest) request).isResolved()) {// To be on the safe side: explicitly delete the parts,// but only actual file parts (for Resin compatibility)try {for (Part part : request.getParts()) {if (request.getFile(part.getName()) != null) {part.delete();}}}catch (Throwable ex) {LogFactory.getLog(getClass()).warn("Failed to perform cleanup of multipart items", ex);}}}}

3.1 StandardMultipartHttpServletRequest

构造方法

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

当resolveLazily为false时,在MultipartResolver#resolveMultipart()阶段并不会进行文件请求解析。也就是说,此时StandardMultipartHttpServletRequest对象的成员变量都是空值。那么,resolveLazily为false时文件请求解析是在什么时候完成的呢?
实际上,在调用StandardMultipartHttpServletRequest接口的getXxx()方法时,内部会判断是否已经完成文件请求解析。如果未解析,就会调用partRequest()方法进行解析,例如:
在这里插入图片描述

StandardMultipartHttpServletRequest#parseRequest

	private void parseRequest(HttpServletRequest request) {try {//调用getParts()方法从请求中获取所有的部分(parts),这些部分可能包括文件和表单数据Collection<Part> parts = request.getParts();//初始化集合存储所有非文件的参数名this.multipartParameterNames = new LinkedHashSet<>(parts.size());//存储每个文件的名称及其对应的MultipartFile对象。MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());//对于每个部分,获取其Content-Disposition头,并解析出文件名。for (Part part : parts) {String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);ContentDisposition disposition = ContentDisposition.parse(headerValue);String filename = disposition.getFilename();//如果文件名不为null,检测是否需要解码,添加到files中if (filename != null) {if (filename.startsWith("=?") && filename.endsWith("?=")) {filename = MimeDelegate.decode(filename);}files.add(part.getName(), new StandardMultipartFile(part, filename));}//处理非文件参数else {this.multipartParameterNames.add(part.getName());}}setMultipartFiles(files);}catch (Throwable ex) {handleParseFailure(ex);}}

3.2 CommonsMultipartResolver

CommonsMultipartResolver根据Apache Commons FileUpload实现,可以处理大文件、文件流等。功能比较强大
首先需要引入了commons-fileupload 依赖:

<dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.4</version> 
</dependency>

编写配置类

@Configuration
public class MultipartResolverConfig {@Beanpublic MultipartResolver multipartResolver() {CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();// 文件请求解析配置:multipartResolver.setXxx()  multipartResolver.setResolveLazily(true);// 设置编码格式multipartResolver.setDefaultEncoding("UTF-8");// 设置写入内存的最大值(单位Byte),超过此值则写入硬盘临时文件multipartResolver.setMaxInMemorySize(1024 * 1024);// 设置上传文件最大值multipartResolver.setMaxUploadSize(1024 * 1024 * 1024);return multipartResolver;}}

CommonsMultipartResolver解析器会根据请求方法和请求头来判断文件请求,源码如下:

	@Overridepublic boolean isMultipart(HttpServletRequest request) {return ServletFileUpload.isMultipartContent(request);}public static final boolean isMultipartContent(HttpServletRequest request) {return !"POST".equalsIgnoreCase(request.getMethod()) ? false : FileUploadBase.isMultipartContent(new ServletRequestContext(request));}

3.2.1CommonsMultipartResolver#resolveMultipart#

CommonsMultipartResolver在解析文件请求时,会将原始请求封装成DefaultMultipartHttpServletRequest对象:

	@Overridepublic MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {Assert.notNull(request, "Request must not be null");// 判断是否是懒加载if (this.resolveLazily) {return new DefaultMultipartHttpServletRequest(request) {@Overrideprotected void initializeMultipart() {MultipartParsingResult parsingResult = parseRequest(request);setMultipartFiles(parsingResult.getMultipartFiles());setMultipartParameters(parsingResult.getMultipartParameters());setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());}};}else {MultipartParsingResult parsingResult = parseRequest(request);return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());}}

3.2.2 CommonsMultipartResolver#parseRequest

CommonsMultipartResolver使用流式处理文件,可以避免内存溢出
问题背景: 文件上传过程中,如果没有采用合适的策略,处理大文件时容易导致内存溢出(OutOfMemoryError)。这通常发生在将整个文件内容加载到内存中再进行处理时。
解决方法: CommonsMultipartResolver 使用了 流式处理,即它不会一次性将整个文件加载到内存中,而是通过流(stream)方式逐步读取文件的内容。这种方式确保了即使上传的是大文件,系统内存也不会被大量占用。这种流式读取方式能有效避免内存溢出问题。

public List<FileItem> parseRequest(RequestContext ctx)throws FileUploadException {List<FileItem> items = new ArrayList<FileItem>();boolean successful = false;try {// 获取文件迭代器,用于遍历 multipart/form-data 请求中的每个部分。每个部分可能是一个文件或者表单字段。FileItemIterator iter = getItemIterator(ctx);FileItemFactory fac = getFileItemFactory();if (fac == null) {throw new NullPointerException("No FileItemFactory has been set.");}while (iter.hasNext()) {/*** 使用 iter.hasNext() 遍历每个文件部分,item 代表当前处理的文件流。* fileName 从当前文件部分中获取文件名(这里没有使用 getName() 是为了避免异常)。* 调用 fac.createItem() 创建一个 FileItem,传入字段名、内容类型、是否是表单字段以及文件名,然后将 fileItem 添加到 items 列表中*/final FileItemStream item = iter.next();// Don't use getName() here to prevent an InvalidFileNameException.final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),item.isFormField(), fileName);items.add(fileItem);try {// 将 item 的输入流(文件数据)通过 Streams.copy 复制到 fileItem 的输出流中,完成文件上传。Streams.copy(item.openStream(), fileItem.getOutputStream(), true);} catch (FileUploadIOException e) {throw (FileUploadException) e.getCause();} catch (IOException e) {throw new IOFileUploadException(format("Processing of %s request failed. %s",MULTIPART_FORM_DATA, e.getMessage()), e);}final FileItemHeaders fih = item.getHeaders();fileItem.setHeaders(fih);}successful = true;return items;} catch (FileUploadIOException e) {throw (FileUploadException) e.getCause();} catch (IOException e) {throw new FileUploadException(e.getMessage(), e);} finally {if (!successful) {for (FileItem fileItem : items) {try {fileItem.delete();} catch (Exception ignored) {// ignored TODO perhaps add to tracker delete failure list somehow?}}}}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/454444.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

十一、数据库配置

一、Navicat配置 这个软件需要破解 密码是&#xff1a;123456&#xff1b; 新建连接》新建数据库 创建一个表 保存出现名字设置 双击打开 把id设置为自动递增 这里就相当于每一次向数据库添加一个语句&#xff0c;会自动增长id一次 二、数据库的增删改查 1、Vs 建一个控…

磁编码器的工作原理和特点

目录 概述 1 磁编码器的构造 1.1 霍尔元件 1.2 永磁体 1.3 永磁体和霍尔元件的配置 2 磁编码器的工作原理 2.1 原理介绍 2.2 电气信号转换成角度 2.3 旋转角度传感器IC 3 磁编码器的特点和主要应用 概述 本文主要介绍磁编码器的构造原理&#xff0c;工作特性和应用特…

C/C++函数调用约定:__cdecl、__stdcall、__fastcall和__thiscall

目录 1.引言 2.常见函数调用约定 2.1.__cdecl 2.2.__stdcall 2.3.__fastcall 2.4.__thiscall 3.几种调用约定比较 4.注意事项 1.引言 在C和C编程中&#xff0c;函数调用约定&#xff08;Calling Convention&#xff09;定义了函数如何接收参数、如何返回值以及由谁来清…

【小沐学Golang】基于Go语言搭建静态文件服务器

文章目录 1、简介2、安装2.1 安装版2.2 压缩版 3、基本操作3.1 go run3.2 go build3.3 go install3.4 go env3.5 go module 4、文件服务器4.1 filebrowser4.2 gohttpserver4.3 goFile 5、FAQ5.1 go.mod 为空5.2 超时 结语 1、简介 https://golang.google.cn/ Go语言诞生于2007…

word表格跨页后自动生成的顶部横线【去除方法】

Hello World! Its been a long time. 这一年重心放在了科研、做事、追寻新的经历上&#xff0c;事有正事、琐事、幸事、哀事&#xff0c;内心与认知成长了一些&#xff0c;思想成熟了几分&#xff0c;技艺也有若干收获。不管怎样&#xff0c;来打个卡吧&#xff0c;纪念一下&…

Web前端高级工程师培训:使用 Node.js 构建一个 Web 服务端程序(3)

11、HTTP 协议 11-1、协议的定义 HTTP 是一种能够获取如 HTML 这样的网络资源的 protocol(通讯协议)。它是在 Web 上进行数据交换的基础&#xff0c;是一种 client-server 协议&#xff0c;也就是说&#xff0c;请求通常是由像浏览器这样的接受方发起的。一个完整的Web文档通…

Tailwind Starter Kit 一款极简的前端快速启动模板

Tailwind Starter Kit 是基于TailwindCSS实现的一款开源的、使用简单的极简模板扩展。会用Tailwincss就可以快速入手使用。Tailwind Starter Kit 是免费开源的。它不会在原始的TailwindCSS框架中更改或添加任何CSS。它具有多个HTML元素&#xff0c;并附带了ReactJS、Vue和Angul…

Docker安装Mysql5.7,解决无法访问DockerHub问题

Docker安装Mysql5.7&#xff0c;解决无法访问DockerHub问题 简介 Docker Hub 无法访问&#xff0c;应用安装失败&#xff0c;镜像拉取超时的解决方案。 摘要 &#xff1a; 当 Docker Hub 无法访问时&#xff0c;可以通过配置国内镜像加速来解决应用安装失败和镜像拉取超时的…

使用爬虫爬取Python中文开发者社区基础教程的数据

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

微信小程序文本收起展开

这里写自定义目录标题 微信小程序文本收起展开常见问题的梯形背景框 微信小程序文本收起展开 参考 https://juejin.cn/post/6963904955262435336 <!-- 常见问题解答 --><view classcontentBottom><view classBottomFirst><text id0 data-id0 class&quo…

python + mitmproxy 爬手机app (1)

起因&#xff0c; 目的: 想爬手机上某鱼。 mitmproxy 简介: 一句话: mitmproxy 就是中间人攻击. (只不过&#xff0c; 你安装&#xff0c;就代表你愿意承担风险。)源码&#xff1a;https://github.com/mitmproxy/mitmproxy文档: https://mitmproxy.org/ 安装过程: 见聊天记…

eCAP超声波测距-ePWM电机调速

目录 eCAP超声波测距 整体框架 关键模块 实验效果 PWM电机调速 DRV8833基本介绍 整体框架 eCAP超声波测距 本实验所用的超声波HC-SR04模块如下图所示&#xff0c;左边为正面图&#xff0c;右边为反面图。 HC-SR04基本工作原理&#xff1a; &#xff08;1&#xff09;采…

spring源码中的,函数式接口,注解@FunctionalInterface

调用方 /org/springframework/beans/factory/support/AbstractBeanFactory.java:333sharedInstance getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It mi…

Kafka之消费者客户端

1、历史上的二个版本 与生产者客户端一样&#xff0c;在Kafka的发展过程当中&#xff0c;消费者客户端主要有两个大的版本&#xff1a; 旧消费者客户端&#xff08;Old Consumer&#xff09;&#xff1a;基于Scala语言开发的版本&#xff0c;又称为Scala消费者客户端。新消费…

rpm 命令

rpm&#xff08;Red Hat Package Manager&#xff09;是 Red Hat Linux 及其衍生发行版&#xff08;如 CentOS、Fedora&#xff09;中用于管理软件包的系统。它允许用户安装、卸载、升级、查询和验证软件包。 一、安装软件包 &#xff08;1&#xff09;安装一个 RPM 软件包&a…

高并发下如何保证接口的幂等性?

前言 接口幂等性问题,对于开发人员来说,是一个跟语言无关的公共问题。本文分享了一些解决这类问题非常实用的办法,绝大部分内容我在项目中实践过的,给有需要的小伙伴一个参考。 不知道你有没有遇到过这些场景: 有时我们在填写某些form表单时,保存按钮不小心快速点了两次…

十二、【智能体】深入剖析:大模型节点的全面解读,举例说明,教你如何在扣子中嵌入代码

大模型节点 大模型节点主要分为5部分&#xff1a; 处理类型 单次批处理 模型类型&#xff1a;目前可以选择的模型有 豆包、通义千问、智谱、MinMax和Kimi输入:此时的参数可以被下面的提示词所用提示词&#xff1a;给大模型使用的提示词输出&#xff1a;经过此大模型处理后的输…

Vehicle Spy3.9如何新建工程—总览

1&#xff1a;写作目的 学习和精通SPY的使用&#xff0c;对于spy&#xff0c;目前主要是通用系用的比较多&#xff0c;本身spy的生产厂家英特佩斯也是美国的公司&#xff0c;除了软件自带教程。中文网上很少能找到相关的中文教程。 故写下这篇文章&#xff0c;帮助自己和大家…

Ubuntu(22.04)本地部署Appsmith

Ubuntu&#xff08;22.04&#xff09;安装Appsmith 简要介绍 Appsmith 是一个开源的低代码开发平台&#xff0c;旨在帮助开发者和非开发者快速构建定制化的内部应用程序和管理工具。通过直观的拖拽界面和丰富的预配置组件&#xff0c;Appsmith 让用户无需编写大量代码即可创建…

软件工程的学习之详细绪论

软件的定义 软件是程序和所有使程序正确运行所需要的相关文档和配置信息。 Software Program Data Document 一、软件危机&#xff1a; 软件开发和维护过程中遇到的一系列严重问题。 二、具体表现&#xff1a; 1、产品不符合用户的实际需要&#xff1b; 2、软件开发生产率…