Java Web中的文件上传和下载

这里主要结合代码说明Java Web中文件上传和下载的机制。

1、文件上传示例说明

这里从一个文件上传的例子,看下文件如何经过http请求上传到服务器端的。
index.jsp

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2021/10/31
  Time: 4:40 PM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <form action="/UploadServletDemo" enctype="multipart/form-data" method="post">
    上传用户:<input type="text" name="username"><br/>
    上传文件1:<input type="file" name="fileUpload1"><br/>
    上传文件2:<input type="file" name="fileUpload2"><br/>

    <input type="submit" value="提交">
  </form>
  </body>
</html>

WEB-INF/web.xml中增加如下的servlet定义和对应的url映射:

<servlet>
    <servlet-name>FileUploadServlet</servlet-name>
    <servlet-class>com.trial.servlet.FileUploadServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>FileUploadServlet</servlet-name>
    <url-pattern>/UploadServletDemo</url-pattern>
</servlet-mapping>

com/trial/servlet/FileUploadServlet.java

package com.trial.servlet;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;

/**
 * Created by chengxia on 2021/11/27.
 */
public class FileUploadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");

        // 获取表单(POST)数据
        ServletInputStream in = request.getInputStream();//此方法得到所有的提交信息,不仅仅只有内容

        // 转换流
        InputStreamReader inReaser = new InputStreamReader(in);

        // 缓冲流
        BufferedReader reader = new BufferedReader(inReaser);
        String str = null;
        while ((str=reader.readLine()) != null){
            System.out.println(str);
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doPost(request, response);
    }
}

注意:在这个逻辑中,我们不做任何处理,只是把http请求体中的内容读取出来,然后输出到控制台。这样,我们可以看到请求从浏览器提交到服务器之后的原始结构。
这样,启动tomcat服务器之后,访问http://localhost:8080/index.jsp

image.png

输入如下表单,并上传文件:


image.png

f1.txt内容如下:

File Content of f1.txt:
f1 content.

f2.txt内容如下:

File Content of f2.txt:
f2 content.

点击提交:


image.png

页面变成了白屏,因为这里没有写响应页面。这时看下控制台的输出(这里使用的浏览器是苹果电脑自带的Safari浏览器),如下:

------WebKitFormBoundary7LVglwc6Wn5QHnN9
Content-Disposition: form-data; name="username"

TestUser
------WebKitFormBoundary7LVglwc6Wn5QHnN9
Content-Disposition: form-data; name="fileUpload1"; filename="f1.txt"
Content-Type: text/plain

File Content of f1.txt:
f1 content.

------WebKitFormBoundary7LVglwc6Wn5QHnN9
Content-Disposition: form-data; name="fileUpload2"; filename="f2.txt"
Content-Type: text/plain

File Content of f2.txt:
f2 content.

------WebKitFormBoundary7LVglwc6Wn5QHnN9--

这里,我们通过浏览器控制台,查看下请求的包头和请求体部分如下:

请求头:
POST /UploadServletDemo HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7LVglwc6Wn5QHnN9
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-cn
Host: localhost:8080
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Referer: http://localhost:8080/
Content-Length: 505
Cookie: JSESSIONID=1B3F46A29FA1E69116EDA870088DA1D7


请求体:
------WebKitFormBoundary7LVglwc6Wn5QHnN9
Content-Disposition: form-data; name="username"

TestUser
------WebKitFormBoundary7LVglwc6Wn5QHnN9
Content-Disposition: form-data; name="fileUpload1"; filename="f1.txt"
Content-Type: text/plain


------WebKitFormBoundary7LVglwc6Wn5QHnN9
Content-Disposition: form-data; name="fileUpload2"; filename="f2.txt"
Content-Type: text/plain


------WebKitFormBoundary7LVglwc6Wn5QHnN9--


改用chrome浏览器访问:


image.png

之后的控制台输出(看着和上面没有什么区别):

------WebKitFormBoundaryFw25OgiIdbAZLcWN
Content-Disposition: form-data; name="username"

TestUser
------WebKitFormBoundaryFw25OgiIdbAZLcWN
Content-Disposition: form-data; name="fileUpload1"; filename="f1.txt"
Content-Type: text/plain

File Content of f1.txt:
f1 content.

------WebKitFormBoundaryFw25OgiIdbAZLcWN
Content-Disposition: form-data; name="fileUpload2"; filename="f2.txt"
Content-Type: text/plain

File Content of f2.txt:
f2 content.

------WebKitFormBoundaryFw25OgiIdbAZLcWN--

为了用于比较,再写一个直接只有简单输入项,不包含文件的表单,提交后,直接看下http请求的输出。
indexForm.jsp如下:

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2021/10/31
  Time: 4:40 PM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <form action="/UploadServletDemo" method="post">
    上传用户:<input type="text" name="username"><br/>
    用户地址:<input type="text" name="useraddress"><br/>

    <input type="submit" value="提交">
  </form>
  </body>
</html>

浏览器访问http://localhost:8080/indexForm.jsp

image.png

控制台输出如下:

username=TestUser&useraddress=TestUserAddress

从这里看出,对于post请求来说,请求中的数据是按照格式存放在请求体中的。只不过如果在表单中指定了enctype="multipart/form-data"这样的话,请求体中的格式稍微有些复杂。每一个表单项(无论是一般的input,还是文件项)之间,被一个随机的字符串(这个随机的字符串在请求的包头中可以看到,前面的浏览器抓包中也可以看出来,上面例子中是Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7LVglwc6Wn5QHnN9)分隔开来,每一项中可以看出是简单的输入表单项,还是文件项(如果是文件项,也可以看出文件名)。

2、解析获得上传文件的例子

从上面的例子可以看出上传文件时http请求的格式和结构,实际上,我们可以根据这个结构去解析http请求体,然后得到用户上传的文件。实际中,我们并不用自己去做,有公共的jar包可以解决这个问题,下面是一个演示如何使用的例子。

2.1 需要的jar包

这里需要用到两个jar包:

  • commons-io-2.11.0.jar
  • commons-fileupload-1.4.jar

下载链接分别如下:

下载对应的jar包之后,放到项目的lib目录下。

2.2 从请求中解析上传的文件

新建一个解析文件的servlet。
com/trial/servlet/FileUploadParseFileServlet.java

package com.trial.servlet;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.UUID;

/**
 * Created by chengxia on 2021/11/27.
 */
public class FileUploadParseFileServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        DiskFileItemFactory fac = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(fac);
        upload.setFileSizeMax(10 * 1024 * 1024);
        upload.setSizeMax(20 * 1024 * 1024);

        if (upload.isMultipartContent(request)) {
            try {
                List<FileItem> list = upload.parseRequest(request);
                for (FileItem item : list) {
                    if (item.isFormField()) {
                        String fileName = item.getFieldName();
                        String value = item.getString("UTF-8");
                        System.out.println("普通表单, " + fileName + ":" + value);
                    } else {
                        //为了避免上传文件重名,在前面拼接一个随机串
                        String name = item.getName();
                        String id = UUID.randomUUID().toString();
                        name = id + name;

                        //上传文件放在一个统一的目录
                        String realPath = getServletContext().getRealPath("/upload");
                        File uploadDir = new File(realPath);
                        if (!uploadDir.exists() && !uploadDir.isDirectory()) {
                            uploadDir.mkdirs();
                            System.out.println("创建上传文件目录: " + realPath);
                        } else {
                            System.out.println("上传文件目录: " + realPath + " 已经存在!");
                        }
                        File file = new File(realPath, name);
                        item.write(file);
                        System.out.println("文件" + item.getName() + "上传到" +file.getAbsolutePath());
                        item.delete();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("不处理!");
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doPost(request, response);
    }
}

WEB-INF/web.xml中新增servlet定义和url映射:

<servlet>
    <servlet-name>FileUploadParseFileServlet</servlet-name>
    <servlet-class>com.trial.servlet.FileUploadParseFileServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>FileUploadParseFileServlet</servlet-name>
    <url-pattern>/FileUploadParseDemo</url-pattern>
</servlet-mapping>

重新写一个jsp页面将文件上传请求,提交给前面的文件解析servlet处理。
indexParseFile.jsp

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2021/10/31
  Time: 4:40 PM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <form action="/FileUploadParseDemo" enctype="multipart/form-data" method="post">
    上传用户:<input type="text" name="username"><br/>
    上传文件1:<input type="file" name="fileUpload1"><br/>
    上传文件2:<input type="file" name="fileUpload2"><br/>

    <input type="submit" value="提交">
  </form>
  </body>
</html>

访问http://localhost:8080/indexParseFile.jsp

image.png

输入表单域:


image.png

点击提交之后,跳转到空白页面:


image.png

可以看到控制台输出:

普通表单, username:TestUser
上传文件目录: /Users/chengxia/Developer/Java/JavaWeb/HelloCherry/out/artifacts/HelloCherry_war_exploded/upload 已经存在!
文件f1.txt上传到/Users/chengxia/Developer/Java/JavaWeb/HelloCherry/out/artifacts/HelloCherry_war_exploded/upload/8628411a-5c7e-485f-9318-89a7a00000c3f1.txt
上传文件目录: /Users/chengxia/Developer/Java/JavaWeb/HelloCherry/out/artifacts/HelloCherry_war_exploded/upload 已经存在!
文件f2.txt上传到/Users/chengxia/Developer/Java/JavaWeb/HelloCherry/out/artifacts/HelloCherry_war_exploded/upload/2315e06e-225f-4efa-b575-f45a6d4af830f2.txt

到电脑上对应的目录检查,可以看到文件确实已经上传成功了。

2.3 注意和提示

2.3.1 依赖的jar包

之前没有引入commons-io-2.11.0.jarcommons-fileupload-1.4.jarjar包,upload.parseRequest(req);报了一个类型不兼容的错,具体提示如下:

The method parseRequest(RequestContext) in the type FileUploadBase is not applicable for the arguments (HttpServletRequest) 

原因就在于,默认导入的是:

import org.apache.tomcat.util.http.fileupload.FileItem; 
import org.apache.tomcat.util.http.fileupload.FileUploadException; 
import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory; 
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;

实际要导的是依赖于commons-io-2.11.0.jarcommons-fileupload-1.4.jarjar的如下类:

import org.apache.commons.fileupload.FileItem; 
import org.apache.commons.fileupload.FileUploadException; 
import org.apache.commons.fileupload.disk.DiskFileItemFactory; 
import org.apache.commons.fileupload.servlet.ServletFileUpload;

导入之后,报错就解决了。

2.3.2 Artifact打包问题

[2022-04-03 02:27:48,547] Artifact JavaEEFileUpload:war exploded: Artifact is being deployed, please wait...
03-Apr-2022 14:27:48.868 严重 [RMI TCP Connection(2)-127.0.0.1] org.apache.catalina.core.ContainerBase.addChildInternal ContainerBase.addChild: start: 
 org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[]]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:162)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:755)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:731)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:717)
    at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:1730)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:300)
    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
    at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
    at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:485)
    at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:434)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:300)
    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
    at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
    at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1468)
    at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:76)
    at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1309)
    at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1401)
    at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:829)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:357)
    at sun.rmi.transport.Transport$1.run(Transport.java:200)
    at sun.rmi.transport.Transport$1.run(Transport.java:197)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
    at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:573)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:688)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.NoClassDefFoundError: org/apache/commons/fileupload/FileItemFactory
    at java.lang.Class.getDeclaredFields0(Native Method)
    at java.lang.Class.privateGetDeclaredFields(Class.java:2583)
    at java.lang.Class.getDeclaredFields(Class.java:1916)
    at org.apache.catalina.util.Introspection.getDeclaredFields(Introspection.java:106)
    at org.apache.catalina.startup.WebAnnotationSet.loadFieldsAnnotation(WebAnnotationSet.java:269)
    at org.apache.catalina.startup.WebAnnotationSet.loadApplicationServletAnnotations(WebAnnotationSet.java:137)
    at org.apache.catalina.startup.WebAnnotationSet.loadApplicationAnnotations(WebAnnotationSet.java:69)
    at org.apache.catalina.startup.ContextConfig.applicationAnnotationsConfig(ContextConfig.java:336)
    at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:776)
    at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:307)
    at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:95)
    at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5262)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145)
    ... 42 more
Caused by: java.lang.ClassNotFoundException: org.apache.commons.fileupload.FileItemFactory
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1352)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1180)
    ... 56 more

查了下,原因就是新添加的lib依赖没有打进Artifact里面。只需要删掉这个Artifact,重新建一个就可以了(选from module),如下图是操作说明:

image.png

2.4 补充1: 文件上传之后,跳转到结果页面

前面文件上传之后,都是到一个空白页面,因为servlet处理请求之后,没有做页面跳转。这里做下补充,跳到一个文件上传结果页面。
首先,写一个文件上传之后转发到jsp结果页面的servlet。
com/trial/servlet/FileUploadParseFileForwardServlet.java

package com.trial.servlet;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Created by chengxia on 2021/11/27.
 */
public class FileUploadParseFileForwardServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        DiskFileItemFactory fac = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(fac);
        upload.setFileSizeMax(10 * 1024 * 1024);
        upload.setSizeMax(20 * 1024 * 1024);

        //记录文件上传信息
        String userName = null;
        List<String> fileList = new ArrayList<String>();

        if (upload.isMultipartContent(request)) {
            try {
                List<FileItem> list = upload.parseRequest(request);
                for (FileItem item : list) {
                    if (item.isFormField()) {
                        String fileName = item.getFieldName();
                        String value = item.getString("UTF-8");
                        userName = value;
                        System.out.println("普通表单, " + fileName + ":" + value);
                    } else {
                        //为了避免上传文件重名,在前面拼接一个随机串
                        String name = item.getName();
                        String id = UUID.randomUUID().toString();
                        name = id + name;

                        //上传文件放在一个统一的目录
                        String realPath = getServletContext().getRealPath("/upload");
                        File uploadDir = new File(realPath);
                        if (!uploadDir.exists() && !uploadDir.isDirectory()) {
                            uploadDir.mkdirs();
                            System.out.println("创建上传文件目录: " + realPath);
                        } else {
                            System.out.println("上传文件目录: " + realPath + " 已经存在!");
                        }
                        File file = new File(realPath, name);
                        item.write(file);
                        System.out.println("文件" + item.getName() + "上传到" +file.getAbsolutePath());
                        fileList.add(item.getName());
                        item.delete();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("不处理!");
        }

        //跳转到结果页面
        request.setAttribute("userName", userName);
        request.setAttribute("fileList", fileList);
        RequestDispatcher view = request.getRequestDispatcher("indexParseFileResult.jsp");
        view.forward(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doPost(request, response);
    }
}

并在WEB-INF/web.xml增加servlet定义和url映射:

<servlet>
    <servlet-name>FileUploadParseFileForwardServlet</servlet-name>
    <servlet-class>com.trial.servlet.FileUploadParseFileForwardServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>FileUploadParseFileForwardServlet</servlet-name>
    <url-pattern>/FileUploadParseForwardDemo</url-pattern>
</servlet-mapping>

再重新写一个提交到这个servlet的文件上传jsp页面。
indexParseFileForward.jsp

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2021/10/31
  Time: 4:40 PM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <form action="/FileUploadParseForwardDemo" enctype="multipart/form-data" method="post">
    上传用户:<input type="text" name="username"><br/>
    上传文件1:<input type="file" name="fileUpload1"><br/>
    上传文件2:<input type="file" name="fileUpload2"><br/>

    <input type="submit" value="提交">
  </form>
  </body>
</html>

再写一个文件上传成功后的结果页面。
indexParseFileResult.jsp

<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.List" %><%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2021/10/31
  Time: 4:40 PM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <%
    String userName = (String)request.getAttribute("userName");
    List<String> fileList = (ArrayList<String>)request.getAttribute("fileList");

    out.print("<h1>" + "用户: " + userName +" 的如下文件已经上传成功:"+ "</h1>");
    for(int i=0; i < fileList.size(); i++){
      out.print("<h2>" + fileList.get(i) + "</h2>");
    }
  %>
  <h2></h2>
  </body>
</html>

这样,访问http://localhost:8080/indexParseFileForward.jsp

image.png

填写表单,上传文件:


image.png

点击提交

image.png

2.5 补充2: 文件从请求中解析只能解析一次

2.5.1 说明

采用前面的jar包使用如下代码从http请求中获取用户上传文件:

try{
    DiskFileItemFactory fac = new DiskFileItemFactory();
    ServletFileUpload upload = new ServletFileUpload(fac);
    upload.setFileSizeMax(10 * 1024 * 1024);
    List<FileItem> list = upload.parseRequest(request);
    for (FileItem item : list) {
        //处理逻辑
    }
}catch (Exception e) {
    e.printStackTrace();
}

这时,有一个特别重要的问题需要注意,原理上说,是从http的流中将客户上传的文件解析出来的,一次api调用解析完成之后,这个流就被消费完了。后面再解析,就什么都拿不到。
参考:

2.5.2 示例

新建一个解析文件两次的测试servlet名为com.trial.servlet.FileUploadParseFileTwiceTestServlet

package com.trial.servlet;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;

/**
 * Created by chengxia on 2021/11/27.
 */
public class FileUploadParseFileTwiceTestServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        DiskFileItemFactory fac1 = new DiskFileItemFactory();
        ServletFileUpload upload1 = new ServletFileUpload(fac1);
        upload1.setFileSizeMax(10 * 1024 * 1024);
        upload1.setSizeMax(20 * 1024 * 1024);
        DiskFileItemFactory fac2 = new DiskFileItemFactory();
        ServletFileUpload upload2 = new ServletFileUpload(fac2);
        upload2.setFileSizeMax(10 * 1024 * 1024);
        upload2.setSizeMax(20 * 1024 * 1024);

        if (upload1.isMultipartContent(request)) {
            try {
                //第一次解析
                List<FileItem> list1 = upload1.parseRequest(request);
                System.out.println("list1: " + list1);

            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("不处理!");
        }

        if (upload2.isMultipartContent(request)) {
            try {
                //第一次解析
                List<FileItem> list2 = upload2.parseRequest(request);
                System.out.println("list2: " + list2);

            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("不处理!");
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doPost(request, response);
    }
}

web.xml中添加servlet的定义和映射:

<servlet>
    <servlet-name>FileUploadParseFileTwiceTestServlet</servlet-name>
    <servlet-class>com.trial.servlet.FileUploadParseFileTwiceTestServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>FileUploadParseFileTwiceTestServlet</servlet-name>
    <url-pattern>/FileUploadParseFileTwiceTestServlet</url-pattern>
</servlet-mapping>

新建一个提交到这个servlet的jsp页面indexParseFileTwice.jsp

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2021/10/31
  Time: 4:40 PM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>$Title$</title>
</head>
<body>
<form action="/FileUploadParseFileTwiceTestServlet" enctype="multipart/form-data" method="post">
    上传用户:<input type="text" name="username"><br/>
    上传文件1:<input type="file" name="fileUpload1"><br/>
    上传文件2:<input type="file" name="fileUpload2"><br/>

    <input type="submit" value="提交">
</form>
</body>
</html>

浏览器访问http://localhost:8080/indexParseFileTwice.jsp,输入并选择文件:

image.png

提交之后,控制台输出如下:

list1: [name=null, StoreLocation=null, size=4 bytes, isFormField=true, FieldName=username, name=f1.txt, StoreLocation=null, size=36 bytes, isFormField=false, FieldName=fileUpload1, name=f2.txt, StoreLocation=null, size=36 bytes, isFormField=false, FieldName=fileUpload2]
list2: []

可以看出确实是第二次解析就什么都读不到了。
即使在第二次解析时使用同一个ServletFileUpload实例,输出结果也一样。如下是一个测试的servlet,com.trial.servlet.FileUploadParseFileTwice1SrvltFlUpldInsTestServlet

package com.trial.servlet;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * Created by chengxia on 2021/11/27.
 */
public class FileUploadParseFileTwice1SrvltFlUpldInsTestServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        DiskFileItemFactory fac = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(fac);
        upload.setFileSizeMax(10 * 1024 * 1024);
        upload.setSizeMax(20 * 1024 * 1024);

        if (upload.isMultipartContent(request)) {
            try {
                //第一次解析
                List<FileItem> list1 = upload.parseRequest(request);
                System.out.println("list1: " + list1);
                //第二次解析
                List<FileItem> list2 = upload.parseRequest(request);
                System.out.println("list2: " + list2);

            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("不处理!");
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doPost(request, response);
    }
}

如果有时间可以自己试下。
到这里,文件上传的例子就演示完毕。

3、文件下载

和文件上传相比,文件下载要容易的多。最简单的,可以直接将文件存储的目录挂到web服务器上,用户直接点击文件路径的链接就可以下载。但是,这样的下载存在两个问题:

  • 浏览器会自动解析,比如下载的文件是一个网页,就会字节在浏览器中下载,而不是作为一个文件下载到本地。
  • 这种方式所有人都可以下,没法进行权限相关的控制。

因此,还是需要写java代码实现对文件的下载,下面是一个例子。
先写一个提交下载文件名的jsp页面。
indexDownloadFile.jsp

<%--
  Created by IntelliJ IDEA.
  User: chengxia
  Date: 2021/10/31
  Time: 4:40 PM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <form action="/DownloadServletDemo" method="post">
    上传文件名:<input type="text" name="filename" maxlength="500" style="width:400px;"><br/>

    <input type="submit" value="提交">
  </form>
  </body>
</html>

然后,写一个接收请求,获取文件名,根据文件名返回文件的servlet。
com/trial/servlet/FileDownloadServlet.java

package com.trial.servlet;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;

/**
 * Created by chengxia on 2021/11/27.
 */
public class FileDownloadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        String fileName = request.getParameter("filename");//获取要下载的文件名
        //第一步:设置响应类型
        response.setContentType("application/force-download");//应用程序强制下载
        //第二读取文件
        String path = getServletContext().getRealPath("/upload/"+fileName);
        InputStream in = new FileInputStream(path);
        //设置响应头,对文件进行url编码
        fileName = URLEncoder.encode(fileName, "UTF-8");
        response.setHeader("Content-Disposition", "attachment;filename="+fileName);
        response.setContentLength(in.available());

        //第三步:读文件写入http响应
        OutputStream out = response.getOutputStream();
        byte[] b = new byte[1024];
        int len = 0;
        while((len = in.read(b))!=-1){
            out.write(b, 0, len);
        }
        out.flush();
        out.close();
        in.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doPost(request, response);
    }
}

在文件WEB-INF/web.xml中添加servlet定义和url映射:

<servlet>
    <servlet-name>FileDownloadServlet</servlet-name>
    <servlet-class>com.trial.servlet.FileDownloadServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>FileDownloadServlet</servlet-name>
    <url-pattern>/DownloadServletDemo</url-pattern>
</servlet-mapping>

访问http://localhost:8080/indexDownloadFile.jsp

image.png

输入要下载的文件名(这里用的是前面文件上传例子中的生成的文件),比如af52bc39-06bc-49f3-b511-eab747e3943bf1.txt

image.png

点击提交,文件就会作为附件从浏览器中下载。

3.1 文件下载说明

resp.setContentType("application/force-download");,设置应用程序强制下载。
Content-disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。Content-disposition其实可以控制用户请求所得的内容存为一个文件的时候提供一个默认的文件名,文件直接在浏览器上显示或者在访问时弹出文件下载对话框。

格式说明:
content-disposition = "Content-Disposition" ":" disposition-type *( ";" disposition-parm )
字段说明:
Content-Disposition为属性名
disposition-type是以什么方式下载,如attachment为以附件方式下载
disposition-parm为默认保存时的文件名
服务端向客户端游览器发送文件时,如果是浏览器支持的文件类型,一般会默认使用浏览器打开,比如txt、jpg等,会直接在浏览器中显示,如果需要提示用户保存,就要利用Content-Disposition进行一下处理,关键在于一定要加上attachment

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容