Servlet & Spring对Multipart数据请求的支持

本文详细介绍了Multipart FormData在HTML表单文件上传中的应用,以及Servlet 3.0和Spring对Multipart请求的支持。通过示例展示了如何在Servlet中处理文件上传,并解释了Servlet 3.0的Multipart配置,如tempdir和maxFileSize。此外,还讨论了Spring的MultipartResolver,包括基于Commons FileUpload和Servlet 3.0 API的解析方式,以及使用Part API处理文件和表单数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考资料
(1)RFC 1867
(2)Java Servlet Specification 3.1;
(3)《Java Web高级编程》;

1. Multipart FormData

Multipart是HTML中表单文件上传的基本格式,一般通过如下方法可以通过HTTP上传文件:

    <form action="_URL_" method="POST" enctype="multipart/form-data">
        <input type="text" name="username" />
        <input type="file" name="userfile1" />
        <input type="submit" value="submit" />
    </form>

有两个地方是使用Multipart的关键:
(1)对于POST请求来说,enctype的默认值是application/x-www-form-urlencoded,而这里要是用multipart/form-data
(2)<input />的type设置为file

1.1 Multipart的数据格式

基于Multipart,请求的每个部分都有指定的边界分隔开,都有一个值为form-data的Content-Disposition和匹配表单输入名称的name
如果是文件类型字段,还将有filename,匹配MIME类型的Content-Type
使用下面的表单提交单个文件和其他文本域:

测试1:单文件上传
    <form action="/s/upload/1" method="post" enctype="multipart/form-data">
        <fieldset>
            <legend>测试1:单文件上传</legend>
            <p><label for="name">名称 </label><input id="name" type="text" name="name" /></p>
            <p><label for="files">文件 </label><input id="files" type="file" name="files" /></p>
            <p><label for="location">地区 </label><input id="location" type="text" name="location" /></p>
            <input type="submit" />
        </fieldset>
    </form>

请求数据内容:

------WebKitFormBoundaryBDujyAl87MaTQd9J
Content-Disposition: form-data; name="name"

串个沙
------WebKitFormBoundaryBDujyAl87MaTQd9J
Content-Disposition: form-data; name="files"; filename="说明.txt"
Content-Type: text/plain


------WebKitFormBoundaryBDujyAl87MaTQd9J
Content-Disposition: form-data; name="location"

地球
------WebKitFormBoundaryBDujyAl87MaTQd9J--

可以看到每个部分有边界像分隔,这个边界值由客户端决定,是随机生成的(这里我使用chrome做的实验,因此可以看到“WebKit”)。

测试2:多文件上传
    <form action="/s/upload/1" method="post" enctype="multipart/form-data">
        <fieldset>
            <legend>测试2:多文件上传</legend>
            <p><label for="name">名称 </label><input id="name1" type="text" name="name" /></p>
            <p><label for="files">文件 </label><input id="files1" type="file" name="files1" multiple="multiple" /></p>
            <p><label for="location">地区 </label><input id="location1" type="text" name="location" /></p>
            <input type="submit" />
        </fieldset>
    </form>

请求头Content-Type:

    Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryZGDkB6nM3LvHX0KI

请求数据内容:

------WebKitFormBoundaryLnO8YOO2DPoP3M6y
Content-Disposition: form-data; name="name"

2个文件
------WebKitFormBoundaryLnO8YOO2DPoP3M6y
Content-Disposition: form-data; name="files1"; filename="说明.txt"
Content-Type: text/plain


------WebKitFormBoundaryLnO8YOO2DPoP3M6y
Content-Disposition: form-data; name="files1"; filename="说明 (copy).txt"
Content-Type: text/plain


------WebKitFormBoundaryLnO8YOO2DPoP3M6y
Content-Disposition: form-data; name="location"

中国
------WebKitFormBoundaryLnO8YOO2DPoP3M6y--

基于chrome得到的结果,没有出现一个Content-type为multipart/mixed的嵌套部分,而是用多个文件字段部分表示。

2. Servlet 3.0对Multipart的支持

在Servlet3.0之前对于文件上传的支持,可以通过Commons FileUpload等第三方工具来实现。Java EE 6中Servlet 3.0新增了对它的支持,这样可以不再依赖第三方库。主要通过HttpServletRequest的getParts()getPart()方法。
结合前面的测试表单,我用一个Servlet和Jsp来演示处理的具体方法:
UploadServlet2:

@WebServlet (
        name = "uploadServlet2",
        urlPatterns = "/upload2",
        loadOnStartup = 1
)
@MultipartConfig (
        fileSizeThreshold = 5_242_880,
        maxFileSize = 20_971_520L,
        maxRequestSize = 41_943_040L
)
public class UploadServlet2 extends HttpServlet {
    private static final Logger logger = LogManager.getLogger();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("/p/upload/upload.jsp").forward(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Collection<Part> parts = req.getParts();
        logger.debug("uploading...");
        req.setAttribute("parts", parts);
        req.getRequestDispatcher("/p/upload/resolvePart.jsp").forward(req, resp);
    }
}

通过ServletContainerInitializer配置:

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        logger.info("ServletContextListener invoked");
        //获取ServletContext对象
        ServletContext servletContext = sce.getServletContext();
        //添加Servlet
        ServletRegistration.Dynamic uploadRegistration = servletContext.addServlet("uploadServlet", UploadServlet.class);
        uploadRegistration.addMapping("/upload/*");
        uploadRegistration.setLoadOnStartup(1);
        MultipartConfigElement configElement = new MultipartConfigElement(".", 52428800,
                52428800, 0);
        uploadRegistration.setMultipartConfig(configElement);
    }

必须启用multipart支持,配置一样有三种方法:注解,编程(ServletContextListener/ServletContainerInitializer),XML,但是基本的配置项是一样的:
location:临时文件目录,一般可以使用默认的由服务器软件决定;
fileSizehreshold:超过这个阈值放入临时文件目录,否则在内存中有垃圾回收处理;
maxFileSizemaxRequestSize

location的配置需要注意,Servlet规范中的说明:

location元被解析为一个绝对路径且默认为 javax.servlet.context.tempdir。如果指定了相对地址,它将是相对于 tempdir 位置。绝对路径与相对地址的测试必须使用 java.io.File.isAbsolute。

3. Spring对Multipart的支持

服务端主要就是解析Multipart数据。

启用Multipart,这里通过Spring的WebApplicationInitializer初始化器进行配置,本质是通过ServletContainerInitializer。启用指定DispatcherServlet的Multipart支持。还是通过Registration配置的和上面的ServletContextListener一样。这里配置了自定义的临时目录,如果指定的目录不存在或是无法访问,通过”.”配置,因为前面引用的Serlvet规范已经说明了,相对目录是基于容器的临时目录的。

Spring同时兼容了基于Commons FileUpload和Servlet 3.0标准API两种方式的解析。

@Order(1)
public class BootStrap implements WebApplicationInitializer {
    /* 略 */
    @Override
    public void onStartup(ServletContext container) throws ServletException {
        /* 略 */
        DispatcherServlet dispatcherServlet = new DispatcherServlet(cgContext);
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
        ServletRegistration.Dynamic dispatcherCG = container.addServlet("cgServlet",
                dispatcherServlet);
        dispatcherCG.setLoadOnStartup(1);
        //文件上传支持
        //设置临时文件目录
        File path = new File(container.getRealPath("/tmp/web_yjh_files/"));

        if(path.exists() || path.mkdirs()) {
            dispatcherCG.setMultipartConfig(new MultipartConfigElement(
                    path.getAbsolutePath(), 200_971_520L, 401_943_040L, 0
            ));
        } else {
            dispatcherCG.setMultipartConfig(new MultipartConfigElement(
                    ".", 200_971_520L, 401_943_040L, 0
            ));
        }
        /* 略 */
    }
}

Spring提供了一个Multipart的解析器:MultipartResolver,因此在@Configuration类中添加一个Bean,有两种选择CommonsMultipartResolverStandardServletMultipartResolver,分别基于Commons File Upload和Servlet 3.0标准API;

    @Bean
    public MultipartResolver multipartResolver() {
        return new CommonsMultipartResolver();
    }

编写Controller method,使用Spring的MultipartFile

    @RequestMapping(value = "upload", method = RequestMethod.POST, produces = "text/html")
    @ResponseBody
    public String upload(String username,
                         @RequestParam(value = "attachment") MultipartFile parts) throws Exception {
        return "success " +
                "name:" + parts.getName() +
                "size:" + parts.getSize() +
                "contentType:" + parts.getContentType() +
                (parts.getContentType() == null ? "" : ("filename" + parts.getOriginalFilename())) +
                "" + IOUtils.toString(parts.getInputStream()) +
                "size:" + parts.getSize();
    }

使用Part API:

Serlvet Part的标准API包括:
(1)name:表单字段名;
(2)size:Part数据内容的大小;
(3)submittedFileName:Servlet 3.1新增方法,获取文件名,之前必须通过getHeader("content-disposition")解析;
(4)contentType:如果<input>类型是type这个值是null,如果是文件,可以获得它的MIME类型;
(5)getHeader:获取指定头;
(6)getInputStream:获取输入流;

对于表单数据的 Content-Disposition,即使没有文件名,也可使用 part 的名称通过 HttpServletRequest 的
getParameter 和 getParameterValues 方法得到 part 的字符串值。

    <h1>Multipart解析</h1>
    <c:forEach items="${parts}" var="part">
      <p>
        <h3>Part: ${part.name}</h3>
        <span>Size: ${part.size}</span><br />
        <span>SubmittedFileName: ${part.submittedFileName},Servlet 3.1新增方法</span><br />
        <span>ContentType: ${part.contentType}</span><br />
        <c:choose>
          <c:when test="${part.contentType == null || part.contentType eq 'text/plain'}">
            <p>
                ${IOUtils.toString(part.inputStream)}
            </p>
          </c:when>
          <c:when test="${part.contentType eq 'application/octet-stream'}">
            <p>
                ${part.getHeader("content-disposition")}
            </p>
          </c:when>
        </c:choose>
      </p>
    </c:forEach>
  <c:import url="upload.jsp" />
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值