Servlet之文件上传与下载

时间:2022-02-19 20:59:43


Servlet 之文件上传与下载

简介:在web项目中常常需要一些上传文件,或者是下载一些文件的功能。

例如:注册表单/保存商品等相关模块!

--à 注册选择头像 / 商品图片

(数据库:存储图片路径 /图片保存到服务器中指定的目录)

 

一、文件的上传

实现web开发中的文件上传功能其实主要分为两步:

1.web页面中添加上上传输入项

2.servlet中读取上传文件的数据,并保存到本地的硬盘中。

 

1.web页面中添加上传输入选项:

表单的method 属性应该设置为post方法,不能使用get方法

 

表单的enctype 属性应该设置为 multipart/form-data 默认类型:enctype="application/x-www-form-urlencoded"

 

表单的 action 属性因该设置为在后端服务器上处理文件上传的Servlet文件(根据该Servletweb.xml<url-pattern></url-pattern>的值)来上传文件

 

上传单个文件,您应该使用单个带有属性 <input type=file/>的标签,

必须要设置name属性,否则浏览器将不会发送上传的文件

若要上传多个,可设置多个该标签,浏览器会为每个 input 标签关联一个浏览按钮但各个表情name属性值不能一致

 

 

注意:必须把formenctype属值设为multipart/form-data    method

     属性设置为post方式。设置该值后,浏览器在上传文件时,将把文件数据附带在http请求消息体中,并使用MIME协议对上传的文件进行描述,以方便接收方对上传数据进行解析和处理。

 

请求方式:

------WebKitFormBoundary3BxAgYMRb8RuMWui    (每个文件的开始)

name是属性filename是对应的文件名

------WebKitFormBoundary3BxAgYMRb8RuMWui  (每个文件的结束)

 

 Servlet之文件上传与下载

 

3.servlet中读取上传文件的数据,并保存到本地的硬盘中。

 

ServletRequest对象提供了一个可以读取流,getInputStream方法,通过这个方法可以读取到客户端提交过来的数据。但是由于用户可能会同时上传多个文件,在servlet段编程直接读取上传的数据,并分别解析出相应的文件数据是一件非常麻烦的事情

 

public class UploadServlet extends HttpServlet {

 

public void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

/*

request.getParameter(""); // GET/POST

request.getQueryString(); // 获取GET提交的数据

request.getInputStream(); // 获取post提交的数据   */

/***********手动获取文件上传表单数据************/

//1. 获取表单数据流

InputStream in =  request.getInputStream();

//2. 转换流

InputStreamReader inStream = new InputStreamReader(in,"UTF-8");

//3. 缓冲流

BufferedReader reader = new BufferedReader(inStream);

// 输出数据

String str = null;

while ((str = reader.readLine()) !=null) {

System.out.println(str);

}

// 关闭

reader.close();

inStream.close();

in.close();

}

输出结果:

------WebKitFormBoundaryGoQviatB7iM1dhPr

Content-Disposition: form-data; name="userName"       【FileItem

 

Jack

------WebKitFormBoundaryGoQviatB7iM1dhPr

Content-Disposition: form-data; name="file_img"; filename="reamde.txt"

Content-Type: text/plain                                     FileItem

 

 

test!!!!!!!!!!!!!

test!!!!!!!!!!!!!

------WebKitFormBoundaryGoQviatB7iM1dhPr--

 

总结:

最终获取数据,要对上面的结果进行解析!

文件上传,在开发中经常用,每次都写解析程序!(工具类)

也可以使用开源的文件上传组件-FileUpload组件!

 

 

如果我们们解析用户上传的文件,如果文件一多,分别解析出相应的文件会非常的麻烦

为方便用户处理文件上传数据,Apache 开源组织提供了一个用来处理表单文件上传的一个开源组件(Commons-fileupload ),该组件性能优异,并且其API使用极其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用Commons-fileupload组件实现。

 

 

使用Commons-fileupload组件实现文件上传

必须导入两个jar包:

1.Commons-fileupload    【文件上传组件核心jar包】

· 可以从 http://commons.apache.org/proper/commons-fileupload/ 下载。

2.Commons-io           【封装了对文件处理的相关工具类】 

(不属于文件上传组件开发的但是Commons-fileupload组件从1.1版本开始,需要commons-io包的支持)

可以从http://commons.apache.org/proper/commons-io/ 下载。

Fileupload组件工作流程(重点)

 Servlet之文件上传与下载

 

重要的两个类:

1. DiskFileltemFactory工厂类

DiskFileltemFactoryFileltem对象的工厂

常用的方法:

public DiskFileItemFactory(int sizeThreshold, java.io.File repository)

构造函数  

public void setSizeThreshold(int sizeThreshold)

设置内存缓冲区的大小,默认值为10K。当上传文件大于缓冲区大小时,fileupload组件将使用临时文件缓存上传文件。

 

public void setRepository(java.io.File repository)

指定临时文件目录,默认值为System.getProperty("java.io.tmpdir").

2. ServletFileUpload对象,将DiskFileltemFactory生成的对象传入其中。然后会将表单中的每个输入项封装成工厂类中生成的Fileltem对象。常用的方法有:

boolean isMultipartContent(HttpServletRequest request)

判断上传表单是否为multipart/form-data类型

List parseRequest(HttpServletRequest request)     

解析request对象,并把表单中的每一个输入项包装成一个fileItem对象,并返回一个保存了所有FileItemlist集合。

setFileSizeMax(long fileSizeMax)

设置上传文件的最大值

setSizeMax(long sizeMax)

设置上传文件总量的最大值

setHeaderEncoding(java.lang.String encoding)

设置编码格式

setProgressListener(ProgressListener pListener)

 

 

 

 

 

具体实现步骤:

1、创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录

2、使用DiskFileItemFactory对象创建ServletFileUpload对象,并设置上传文件的大小限制。

3、调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容的List对象。

4、对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件

为普通表单字段,则调用getFieldNamegetString方法得到字段名和字段值

为上传文件,则调用getInputStream方法得到数据输入流,从而读取上传数据。

编码实现文件上传

 

 

小技巧:

每次动态增加一个文件上传输入框,都把它和删除按纽放置在一个单独的div中,并对删除按纽的onclick事件进行响应,使之删除删除按纽所在的div

如:

this.parentNode.parentNode.removeChild(this.parentNode);

 

 

 

针对上传文件的细节处理:(重点)

1.中文文件乱码问题

文件名中文乱码问题,可调用ServletUpLoadersetHeaderEncoding方法,或者设置requestsetCharacterEncoding属性

2.临时文件的删除问题

由于文件大小超出DiskFileItemFactory.setSizeThreshold方法设置的内存缓冲区的大小时,Commons-fileupload组件将使用临时文件保存上传数据,因此在程序结束时,务必调用FileItem.delete方法删除临时文件。

Delete方法的调用必须位于流关闭之后,否则会出现文件占用,而导致删除失败的情况。

3.文件存放位置

为保证服务器安全,上传文件应保存在应用程序的WEB-INF目录下,或者不受WEB服务器管理的目录。

为防止多用户上传相同文件名的文件,而导致文件覆盖的情况发生,文件上传程序应保证上传文件具有唯一文件名。

为防止单个目录下文件过多,影响文件读写速度,处理上传文件的程序应根据可能的文件上传总量,选择合适的目录结构生成算法,将上传文件分散存储。

 

4. ProgressListener显示上传进度

 

ProgressListener progressListener = new ProgressListener() {

public void update(long pBytesRead, long pContentLength, int pItems) {

 

System.out.println("到现在为止,  " + pBytesRead + " 字节已上传,总大小为 "

  + pContentLength);

}

};

upload.setProgressListener(progressListener);

 

KB为单位显示上传进度

long temp = -1;   //temp注意设置为类变量

long ctemp = pBytesRead /1024;

if (mBytes == ctemp)  

return;

temp = mBytes;

 


 

 

具体代码实现

package com.runoob.test;

import java.io.File;import java.io.IOException;import java.io.PrintWriter;import java.util.List;

 import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;

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

 

/**

 * Servlet implementation class UploadServlet

 */@WebServlet("/UploadServlet")public class UploadServlet extends HttpServlet {

    private static final long serialVersionUID= 1L;

     

    // 上传文件存储目录

    private static final String UPLOAD_DIRECTORY= "upload";

 

    // 上传配置

    private static final int MEMORY_THRESHOLD   = 1024 * 1024 * 3;  // 3MB

    private static final int MAX_FILE_SIZE      = 1024 * 1024 * 40; // 40MB

    private static final int MAX_REQUEST_SIZE   = 1024 * 1024 * 50; // 50MB

 

    /**

     * 上传数据及保存文件

     */

    protected void doPost(HttpServletRequest request,

        HttpServletResponse response) throws ServletException, IOException {

        // 检测是否为多媒体上传

        if (!ServletFileUpload.isMultipartContent(request)) {

            // 如果不是则停止

            PrintWriter writer= response.getWriter();

            writer.println("Error: 表单必须包含 enctype=multipart/form-data");

            writer.flush();

            return;

        }

 

        // 配置上传参数

        DiskFileItemFactory factory= new DiskFileItemFactory();

        // 设置内存临界值 - 超过后将产生临时文件并存储于临时目录中

        factory.setSizeThreshold(MEMORY_THRESHOLD);

        // 设置临时存储目录

        factory.setRepository(new File(System.getProperty("java.io.tmpdir")));

 

        ServletFileUpload upload= new ServletFileUpload(factory);

         

        // 设置最大文件上传值

        upload.setFileSizeMax(MAX_FILE_SIZE);

         

        // 设置最大请求值 (包含文件和表单数据)

        upload.setSizeMax(MAX_REQUEST_SIZE);

 

        // 中文处理

        upload.setHeaderEncoding("UTF-8"); 

 

        // 构造临时路径来存储上传的文件

        // 这个路径相对当前应用的目录

        String uploadPath= request.getServletContext().getRealPath("./") + File.separator+ UPLOAD_DIRECTORY;

       

         

        // 如果目录不存在则创建

        File uploadDir= new File(uploadPath);

        if (!uploadDir.exists()) {

            uploadDir.mkdir();

        }

 

        try {

            // 解析请求的内容提取文件数据

            @SuppressWarnings("unchecked")

            List<FileItem> formItems= upload.parseRequest(request);

 

            if (formItems!= null && formItems.size() > 0) {

                // 迭代表单数据

                for (FileItem item: formItems) {

                    // 处理不在表单中的字段

                    if (!item.isFormField()) {

                        String fileName= new File(item.getName()).getName();

                        String filePath= uploadPath + File.separator+ fileName;

                        File storeFile= new File(filePath);

                        // 在控制台输出文件的上传路径

                        System.out.println(filePath);

                        // 保存文件到硬盘

                        item.write(storeFile);

                        request.setAttribute("message",

                            "文件上传成功!");

                    }

                }

            }

        } catch (Exception ex) {

            request.setAttribute("message",

                    "错误信息: " + ex.getMessage());

        }

        // 跳转到 message.jsp

        request.getServletContext().getRequestDispatcher("/message.jsp").forward(

                request, response);

}}

 

二、文件下载

Web应用中实现文件下载的两种方式:

1.超链接直接指向下载资源

2.程序实现下载需要设置两个响应头告诉页面响应的类型:

Content-Type响应头

设置Content-Type的值为:application/x-msdownload

response.setContentType(application/x-msdownload);

Web服务器需要告诉浏览器其所输入的内容类型,而是一个要保存到本地的下载文件)

Content-Disposition报头

 

Web 服务器希望浏览器不直接处理相应的实体内容,而是由用户选择将相应的实体内容保存到一个文件中,所以需要设置Content-Disposition报头。该报头指定了接收程序处理数据内容的方式,在HTTP 应用中只有attachment 是标准方式,attachment表示要求用户干预。在 attachment后面还可以指定 filename参数,该参数是服务器建议浏览器将实体内容保存到文件中的文件名称。在设置 Content-Dispostion之前一定要指定 Content-Type.

 

String fileName =   (获取的文件名防止传到浏览器是发生乱码问题)

//设置attachment标准方式,并且将文件名正确转码

String str =attachment; filename=+java.net.URLEncoder.Encode(fileName,UTF-8);

response.setHeader(Content-Dispostioin,str);

 

 

下载文件流的选择

 

因为要下载的文件可以是各种类型的文件,所以要将文件传送给客户端,其相应内容应该被当做二进制来处理,所以应该调用 response.getOutputStream()方法返回ServeltOutputStream 对象来向客户端写入文件内容。

 

ServletOutputStream sos = response.getOutputStream();

byte[] data = new byte[2048];

Int len = -1;

While((len = is.read(data))!=-1){

Sos.write(data,0,len);

}

 

 

java案例

 

Index.jsp

<body>

   <a href="${pageContext.request.contextPath }/upload.jsp">文件上传</a>    

   <a href="${pageContext.request.contextPath }/fileServlet?method=downList">文件下载</a> 

  

  </body>

Upload.jsp

<body>

    <form name="frm_test" action="${pageContext.request.contextPath }/fileServlet?method=upload" method="post" enctype="multipart/form-data">

      <%--<input type="hidden" name="method" value="upload">--%>

      

      用户名:<input type="text" name="userName">  <br/>

     文件:   <input type="file" name="file_img">   <br/>

    

     <input type="submit" value="提交">

     </form>

  </body>

Downlist.jsp

<body>

<table border="1" align="center">

<tr>

<th>序号</th>

<th>文件名</th>

<th>操作</th>

</tr>

<c:forEach var="en" items="${requestScope.fileNames}"varStatus="vs">

<tr>

<td>${vs.count }</td>

<td>${en.value }</td>

<td>

<%--<a href="${pageContext.request.contextPath }/fileServlet?method=down&..">下载</a>--%>

<!-- 构建一个地址  -->

<c:url var="url" value="fileServlet">

<c:param name="method" value="down"></c:param>

<c:param name="fileName" value="${en.key}"></c:param>

</c:url>

<!-- 使用上面地址 -->

<a href="${url }">下载</a>

</td>

</tr>

</c:forEach>

</table>  

  </body>

FileServlet.java

 

/**

 * 处理文件上传与下载

 * @author Jie.Yuan

 *

 */

public class FileServlet extends HttpServlet {

 

public void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

 

// 获取请求参数: 区分不同的操作类型

String method = request.getParameter("method");

if ("upload".equals(method)) {

// 上传

upload(request,response);

}

else if ("downList".equals(method)) {

// 进入下载列表

downList(request,response);

}

else if ("down".equals(method)) {

// 下载

down(request,response);

}

}

/**

 * 1. 上传

 */

private void upload(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

try {

// 1. 创建工厂对象

FileItemFactory factory = new DiskFileItemFactory();

// 2. 文件上传核心工具类

ServletFileUpload upload = new ServletFileUpload(factory);

// 设置大小限制参数

upload.setFileSizeMax(10*1024*1024); // 单个文件大小限制

upload.setSizeMax(50*1024*1024); // 总文件大小限制

upload.setHeaderEncoding("UTF-8"); // 对中文文件编码处理

 

// 判断

if (upload.isMultipartContent(request)) {

// 3. 把请求数据转换为list集合

List<FileItem> list = upload.parseRequest(request);

// 遍历

for (FileItem item : list){

// 判断:普通文本数据

if (item.isFormField()){

// 获取名称

String name = item.getFieldName();

// 获取值

String value = item.getString();

System.out.println(value);

}

// 文件表单项

else {

/******** 文件上传***********/

// a. 获取文件名称

String name = item.getName();

// ----处理上传文件名重名问题----

// a1. 先得到唯一标记

String id = UUID.randomUUID().toString();

// a2. 拼接文件名

name = id + "#" + name;

// b. 得到上传目录

String basePath = getServletContext().getRealPath("/upload");

// c. 创建要上传的文件对象

File file = new File(basePath,name);

// d. 上传

item.write(file);

item.delete();  // 删除组件运行时产生的临时文件

}

}

}

} catch (Exception e) {

e.printStackTrace();

}

}

 

/**

 * 2. 进入下载列表

 */

private void downList(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

// 实现思路:先获取upload目录下所有文件的文件名,再保存;跳转到down.jsp列表展示

//1. 初始化map集合Map<包含唯一标记的文件名,简短文件名>  ;

Map<String,String> fileNames = new HashMap<String,String>();

//2. 获取上传目录,及其下所有的文件的文件名

String bathPath = getServletContext().getRealPath("/upload");

// 目录

File file = new File(bathPath);

// 目录下,所有文件名

String list[] = file.list();

// 遍历,封装

if (list != null && list.length > 0){

for (int i=0; i<list.length; i++){

// 全名

String fileName = list[i];

// 短名

String shortName = fileName.substring(fileName.lastIndexOf("#")+1);

// 封装

fileNames.put(fileName, shortName);

}

}

// 3. 保存到request

request.setAttribute("fileNames", fileNames);

// 4. 转发

request.getRequestDispatcher("/downlist.jsp").forward(request, response);

 

}

 

/**

 *  3. 处理下载

 */

private void down(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

// 获取用户下载的文件名称(url地址后追加数据,get)

String fileName = request.getParameter("fileName");

fileName = new String(fileName.getBytes("ISO8859-1"),"UTF-8");

// 先获取上传目录路径

String basePath = getServletContext().getRealPath("/upload");

// 获取一个文件流

InputStream in = new FileInputStream(new File(basePath,fileName));

// 如果文件名是中文,需要进行url编码

fileName = URLEncoder.encode(fileName, "UTF-8");

// 设置下载的响应头

response.setHeader("content-disposition","attachment;fileName=" + fileName);

// 获取response字节流

OutputStream out = response.getOutputStream();

byte[] b = new byte[1024];

int len = -1;

while ((len = in.read(b)) != -1){

out.write(b, 0, len);

}

// 关闭

out.close();

in.close();

}

public void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

this.doGet(request, response);

}

 

}