Servlet 之文件上传与下载
简介:在web项目中常常需要一些上传文件,或者是下载一些文件的功能。
例如:注册表单/保存商品等相关模块!
--à 注册选择头像 / 商品图片
(数据库:存储图片路径 /图片保存到服务器中指定的目录)
一、文件的上传
实现web开发中的文件上传功能其实主要分为两步:
1.在web页面中添加上上传输入项
2.在servlet中读取上传文件的数据,并保存到本地的硬盘中。
1.在web页面中添加上传输入选项:
l 表单的method 属性应该设置为post方法,不能使用get方法
l 表单的enctype 属性应该设置为 multipart/form-data (默认类型:enctype="application/x-www-form-urlencoded")
l 表单的 action 属性因该设置为在后端服务器上处理文件上传的Servlet文件。(根据该Servlet在web.xml中<url-pattern></url-pattern>的值)来上传文件
l 上传单个文件,您应该使用单个带有属性 <input type=”file”/>的标签,
必须要设置name属性,否则浏览器将不会发送上传的文件
若要上传多个,可设置多个该标签,浏览器会为每个 input 标签关联一个浏览按钮但各个表情name属性值不能一致
注意:必须把form的enctype属值设为multipart/form-data method
属性设置为post方式。设置该值后,浏览器在上传文件时,将把文件数据附带在http请求消息体中,并使用MIME协议对上传的文件进行描述,以方便接收方对上传数据进行解析和处理。
请求方式:
------WebKitFormBoundary3BxAgYMRb8RuMWui (每个文件的开始)
name是属性filename是对应的文件名
------WebKitFormBoundary3BxAgYMRb8RuMWui (每个文件的结束)
3.在servlet中读取上传文件的数据,并保存到本地的硬盘中。
在Servlet中Request对象提供了一个可以读取流,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组件工作流程(重点)
重要的两个类:
1. DiskFileltemFactory工厂类
DiskFileltemFactory是Fileltem对象的工厂
常用的方法:
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对象,并返回一个保存了所有FileItem的list集合。
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方法判断是否是上传文件
为普通表单字段,则调用getFieldName、getString方法得到字段名和字段值
为上传文件,则调用getInputStream方法得到数据输入流,从而读取上传数据。
编码实现文件上传
小技巧:
每次动态增加一个文件上传输入框,都把它和删除按纽放置在一个单独的div中,并对删除按纽的onclick事件进行响应,使之删除删除按纽所在的div。
如:
this.parentNode.parentNode.removeChild(this.parentNode);
针对上传文件的细节处理:(重点)
1.中文文件乱码问题
文件名中文乱码问题,可调用ServletUpLoader的setHeaderEncoding方法,或者设置request的setCharacterEncoding属性
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); }
}
|