详解基于jsp的文件的上传下载

文件的上传和下载的本质就是文本复制的过程,实现web层面的文件上传功能,需要完成如下两步的操作:

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

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

因此在技术层面上,在Java中一定会用到IO操作,主要以二进制方式读写。

那么如何在Web页面中添加上传输入项?

1.传统方式下,在前端页面中将input标签的type属性设置为file即<input type = “file”>,但是对于上传文件字段不同的浏览器有着不同的解析方式,例如:

IE6:
upfile=c:\aa\bb\a.JPG
非IE6: 

upfile=a.JPG

可以看出IE6下包含文件的绝对路径但是非IE6下不能识别出文件的相对路径所以可能识别不出文件,因此这个方式不行

2.把前端页面中form的enctype属值设为multipart/form-data.设置该值后,浏览器在上传文件时,将把文件数据附带在http请求消息体中,并使用MIME协议对上传的文件进行描述,以方便接收方对上传数据进行解析和处理。在servlet中request对象提供了一个getInputStream方法,通过这个方法可以读取到客户端提交过来的数据。但由于用户可能会同时上传多个文件,在servlet端编程直接读取上传数据,并分别解析出相应的文件数据是一项非常麻烦的工作,解析方式为

   

InputStream is = request.getInputStream();
OutputStream os = response.getOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while((len = is.read(buf))>0){
	os.write(buf, 0, len);
}
is.close();
os.close();
以上两种方式都比较麻烦,因此为方便用户处理文件上传数据,Apache 开源组织提供了一个用来处理表单文件上传的一个开源组件(commons-fileupload),该组件性能优异,并且其API使用极其简单,可以让开发人员轻松实现web文件上传功能,因此在web开发中实现文件上传功能,通常使用commons-fileupload组件实现。

使用commons-fileupload组件实现文件上传,需要导入该组件相应的支撑jar包:commons-fileupload和commons-io。commons-io 不属于文件上传组件的开发jar文件,但commons-fileupload 组件从1.1 版本开始,它工作时需要commons-io包的支持,可以在此处下载:http://download.csdn.net/detail/it_tingge/9169347

本文中文件的上传下载功能可以实现控制上传文件的数量,实现对文件的大小和文件类型的控制,可以检测用户是否上传文件。

本文所使用的工程(完整源代码下载地址在博文后面)目录为:

一、前端页面

前端页面主要实现的功能如下图所示

1.上传页面主要是一个form表单

<form 
  		action="${pageContext.request.contextPath}/UploadServlet" 
  		method="POST"
  		enctype="multipart/form-data">
  		<table border="1" align="center">
  			<caption>文件上传</caption>
  			<tr>
  				<th>上传用户</th>
  				<td><input type="text" name="username"/></td>
  			</tr>
  			<tr>
  				<th></th>
  				<td>
  					<div id="outDiv">
  						
  						<div>
							<input type="file" name="upfile"/>
							<input type="button" value="删除"/>  							
  						</div>
  						
  					</div>
  				</td>
  			</tr>
  			<tr>
  				<th></th>
  				<td>
  					<input 
  						type="button" 
  						value="添加上传文件"
  						οnclick="addLine(this)"
  					/>
  				</td>
  			</tr>
  			<tr>
  				<td colspan="2" align="center">
  					<input type="submit" value="上传"/>
  					<a href="${pageContext.request.contextPath}/ListFileServlet">
  						显示下载文件
  					</a>
  				</td>
  			</tr>
  		</table>
  	</form>
为了实现对上传文件数量的控制,需要通过JS进行控制,代码实现如下所示

	<script type="text/javascript">
  		//全局变量
  		var time = 0;
  		function addLine(addButton){
  			//创建内部div对象
  			var divElement = document.createElement("div");
  			//创建input对象[file类型]
  			var inputElement1 = document.createElement("input");
  			inputElement1.type="file";
  			inputElement1.name="upfile";
  			//创建input对象[button类型]
  			var inputElement2 = document.createElement("input");
  			inputElement2.type="button";
  			inputElement2.value="删除";
  			//对删除按钮添加事件监听
  			inputElement2.οnclick=function(){
  				//取得该按钮所在行的直接父元素
  				var divElement = this.parentNode.parentNode;
  				//通过父元素删除直接子元素
  				divElement.removeChild(this.parentNode);
  				time--;
  				if(time < 5){
  					//按钮生效
  					addButton.disabled=false;
  					//addButton.style.visibility="visible";
  				}
  			}
  			//依次将file类型和button类型的input对象加入到内部div对象中
  			divElement.appendChild(inputElement1);
  			divElement.appendChild(inputElement2);
  			//再次内部div对象加入到外部div对象
  			var outDivElement = document.getElementById("outDiv");
  			outDivElement.appendChild(divElement);
  			time++;
  			if(time == 5){
  				//将按钮失效
  				addButton.disabled=true;
  				//addButton.style.visibility="hidden";
  			}	
  		}
  	</script>


2.下载页面

下载页面主要是一个表格列出上传的文件并提供下载功能,其代码实现为

<table border="1" align="center">
  		<caption>下载文件列表</caption>
  		<tr>
  			<th>文件名</th>
  			<th>操作</th>
  		</tr>
  		<c:forEach var="entry" items="${requestScope.map}">
	  		<tr>
	  			<td>${entry.value}</td>	
	  			<td>
	  				<c:url var="myURL" value="/DownloadServlet">
	  					<c:param name="uuidFileName" value="${entry.key }"></c:param>
	  				</c:url>
	  				
	  				<a  href="${myURL}" 
	  					style="text-decoration:none">
	  					下载
	  				</a>
	  			</td>
	  		</tr>
  		</c:forEach>
  	</table>


二、后台功能实现

Fileupload组件工作流程为

其核心API有
1.DiskFileItemFactory
DiskFileItemFactory 是创建 FileItem 对象的工厂,这个工厂类常用方法:
public void setSizeThreshold(int sizeThreshold) 
设置内存缓冲区的大小,默认值为10K。当上传文件大于缓冲区大小时, fileupload组件将使用临时文件缓存上传文件。
Public void setRepository(java.io.File repository) 
指定临时文件目录,默认值为System.getProperty("java.io.tmpdir").
public DiskFileItemFactory(int sizeThreshold, java.io.File repository) 构造函数
2.ServletFileUpload 
负责处理上传的文件数据,并将表单中每个输入项封装成一个 FileItem 对象中。常用方法有:
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) 
设置编码格式,解决上传中文名文件的问题数
3.实现步骤
创建DiskFileItemFactory对象,设置缓冲区大小和临时文件目录
使用DiskFileItemFactory 对象创建ServletFileUpload对象,并设置上传文件的大小限制。
调用ServletFileUpload.parseRequest方法解析request对象,得到一个保存了所有上传内容FileItem的List对象。
对list进行迭代,每迭代一个FileItem对象,调用其isFormField方法判断是否是上传文件
为普通表单字段,则调用getFieldName、getString方法得到字段名和字段值
为上传文件,则调用getName 、 getInputStream方法得到数据输入流,从而读取上传数据。
编码实现文件上传
4.中文文件名乱码问题
文件名中文乱码问题,可调用ServletFileUpload的setHeaderEncoding方法,或者设置request的setCharacterEncoding属性 
5临时文件的删除问题
由于文件大小超出DiskFileItemFactory.setSizeThreshold方法设置的内存缓冲区的大小时,commons-fileupload组件将使用临时文件保存上传数据,因此在程序结束时,务必调用FileItem.delete方法删除临时文件。
delete方法的调用必须位于流关闭之后,否则会出现文件占用,而导致删除失败的情况。
6文件存放名字
为防止多用户上传相同文件名的文件,而导致文件覆盖的情况发生,文件上传程序应保证上传文件具有唯一文件名。
7.文件存放位置
为保证服务器安全,上传文件应保存在应用程序的WEB-INF目录下,或者不受WEB服务器管理的目录。
为防止单个目录下文件过多,影响文件读写速度,处理上传文件的程序应根据可能的文件上传总量,选择合适的目录结构生成算法,将上传文件分散存储到不同的目录
 .文件上传到关键代码为

package com.uploaddownload.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.UUID;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

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

import com.uploaddownload.domain.User;
import com.uploaddownload.exception.NoUpfileException;

public final class UploadUtil {
	// 取的上传文件使用的临时目录
	public static final String tempPath = "/WEB-INF/temp";
	// 取的上传文件使用的真实目录
	public static final String uploadPath = "/WEB-INF/upload";
	
	//取的真实文件名
	public static String getRealFileName(String realFileName){
		int index = realFileName.lastIndexOf("\\");
		if(index>=0){
			//IE6浏览器
			realFileName = realFileName.substring(index+1);
		}
		return realFileName;
	}
	
	//取的uuid文件名
	public static String makeUuidFilePath(String uuidFileName, String uploadPath) {
		String uuidFilePath = null;
		int code = uuidFileName.hashCode();
		int dir1 = code & 0xF;
		int dir2 = code >> 4 & 0xF;
		
		File file = new File(uploadPath+"/"+dir1+"/"+dir2);
		
		if(!file.exists()){
			//如果该目录未存在
			//一次性创建N层目录
			file.mkdirs();
		}
		uuidFilePath = file.getPath();
		return uuidFilePath;
	}
	//去的upload/目录下的分散目录
	public static String makeUuidFileName(String realFileName) {
		return UUID.randomUUID().toString()+"_"+realFileName;
			
	}
	//文件复制
	public static void  doSave(InputStream is ,String uuidFilePath,String uuidFileName){
		//取的文件输出流
		OutputStream os = null;
		try {
			os = new FileOutputStream(uuidFilePath+"/"+uuidFileName);
			byte[] buf = new byte[1024];
			int len = 0;
			while((len = is.read(buf))>0){
				os.write(buf, 0, len);
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			
				if(is!=null){
					try {
						is.close();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				if(os!=null){
					try {
						os.close();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			
		}
	}
	
	//将上传文件封装成JavaBean对象中
	@SuppressWarnings({ "static-access", "unchecked" })
	public static User doUpload(HttpServletRequest request) throws Exception{
		User user = new User();
		//取的上传文件使用的临时目录
		String tempPath = request.getSession().getServletContext().getRealPath("/WEB-INF/temp");
		//创建上传文件工厂
		DiskFileItemFactory factory = new DiskFileItemFactory();
		//设置内存中缓存区的大小,默认10K
		factory.setSizeThreshold(10*1024);
		//设置上传文件临时存放的目录
		factory.setRepository(new File(tempPath));
		//创建上传文件对象[核心]
		ServletFileUpload upload = new ServletFileUpload(factory);
		//设置上传文件的中文编码方式
		upload.setHeaderEncoding("UTF-8");
		//客户端上传文件是否使用MIME协议
		boolean flag = upload.isMultipartContent(request);
		if(!flag){
			//不是以MIME协议上传文件
			throw new ServletException();
		}else{
			//是以MIME协议上传文件,解析request中的所有上传内容
			//将每个内容封装成一个对象FileItem,
			//FileItem代表普通字段和上传字段两类
		
				List<FileItem> fileItemList = upload.parseRequest(request);
				for(FileItem fileItem:fileItemList){
					if(fileItem.isFormField()){
						//必定是普通字段
						String fieldName = fileItem.getFieldName();
						//解决中文乱码问题
						String fieldValue = fileItem.getString("UTF-8");
						
						user.setUsername(fieldValue);
						System.out.println(fieldName+":"+fieldValue);
					}else {
						//必定是上传字段
						if(fileItem.getSize()==0){
							throw new NoUpfileException();
						}
						/*String realFileName = UploadUtil.getRealFileName(fileItem.getName());
						//只能上传JPG文件
						if(!realFileName.endsWith("JPG")){
							throw new UpfileTypeException();
						}
						
						
						//只有上传<=200K的文件
						if(fileItem.getSize() > 200 * 1024){
							throw new UpfileSizeException();
						}*/
						//封装到JavaBean
						user.getUpfileList().add(fileItem);
					}
				}
			
		}
		
		return user;
	}

	public static void doSave(User user, String uploadPath) throws Exception {
		
		List<FileItem> fileItemList = user.getUpfileList();
		
		for(FileItem fileItem:fileItemList){
			
			//取得输入流
			InputStream is = fileItem.getInputStream();
			//取得真实文件名
			String realFileName = fileItem.getName();
			realFileName = UploadUtil.getRealFileName(realFileName);
			//取的UUID文件名
			String uuidFileName = UploadUtil.makeUuidFileName(realFileName);
			//取的UUID文件路径
			String uuidFilePath = UploadUtil.makeUuidFilePath(uuidFileName, uploadPath);
			//保存
			UploadUtil.doSave(is, uuidFilePath, uuidFileName);
			fileItem.delete();
		}
		
		
	}
		
}

列出上传文件的关键代码为

package com.uploaddownload.download;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

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

import com.uploaddownload.util.UploadUtil;
/**
 * 显示下载文件
 * @author LiChunting
 *
 */
public class ListFileServlet extends HttpServlet {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		//定位下载文件的目录
		String uploadPath = this.getServletContext().getRealPath(UploadUtil.uploadPath);
		//创建Map<UUID文件名,真实文件名>
		Map<String,String> map = new HashMap<String,String>();
		//取的下载文件的相关信息
		getFiles(uploadPath,map);
		//转发到list.jsp显示可供下载的文件
		request.setAttribute("map", map);
		request.getRequestDispatcher("/WEB-INF/list.jsp").forward(request, response);
		
	}
	//递归寻找所有可供下载的文件
	private void getFiles(String uploadPath,Map<String,String> map){
		File file = new File(uploadPath);
		//如果file表示文件
		if(file.isFile()){//出口
			//取的文件名,即UUID文件名
			String uuidFileName = file.getName();
			
			int index = uuidFileName.indexOf("_");
			
			String realFileName = uuidFileName.substring(index+1);
			
			//存放到Map集合中
			map.put( uuidFileName,realFileName);
			
		}else{
			//必定是目录
			//取的该目录下的所有内容
			File[] files = file.listFiles();
			for(File f:files){
				//递归调自己
				getFiles(f.getPath(),map);
			}
		}
	}

}

下载

超链接直接指向下载资源
程序实现下载需设置两个响应头:
设置Content-Type 的值为:application/x-msdownload。Web 服务器需要告诉浏览器其所输出的内容的类型不是普通的文本文件或 HTML 文件,而是一个要保存到本地的下载文件。
Web 服务器希望浏览器不直接处理相应的实体内容,而是由用户选择将相应的实体内容保存到一个文件中,这需要设置 Content-Disposition 报头。该报头指定了接收程序处理数据内容的方式,在 HTTP 应用中只有 attachment 是标准方式,attachment 表示要求用户干预。在 attachment 后面还可以指定 filename 参数,该参数是服务器建议浏览器将实体内容保存到文件中的文件名称。在设置 Content-Dispostion 之前一定要指定 Content-Type.

下载的关键代码为

package com.uploaddownload.download;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;

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

import com.uploaddownload.util.UploadUtil;

public class DownloadServlet extends HttpServlet {


	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String uuidFileName = request.getParameter("uuidFileName");
		byte[] buf = uuidFileName.getBytes("ISO8859-1");
		uuidFileName = new String(buf,"UTF-8");
		
		int index = uuidFileName.lastIndexOf("_");
		
		String realFileName = uuidFileName.substring(index+1);
		
		
		response.setHeader("content-disposition","attachment;filename="+URLEncoder.encode(realFileName,"UTF-8"));
		
		String uploadPath = this.getServletContext().getRealPath(UploadUtil.uploadPath);
		String uuidFilePath = UploadUtil.makeUuidFilePath(uuidFileName, uploadPath);
		InputStream is = new FileInputStream(uuidFilePath+"/"+uuidFileName);
		OutputStream os = response.getOutputStream();
		buf = new byte[1024];
		int len = 0;
		while((len = is.read(buf))>0){
			os.write(buf,0,len);
		}
		is.close();
		os.close();
	}

}
到这,文件的上传下载功能都实现了,完整的代码可以在http://download.csdn.net/detail/it_tingge/9169905下载。

谢谢您的阅读!


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本代码主要应用的是jsp技术,而实现的文件上传功能,这个功能也是比较常见的,也是比较常用的,更是在网络中比较流行的。 技术为创建显示动态生成内容的Web页面提供了一个简捷而快速的方法。JSP技术的设计目的是使得构造基于Web的应用程序更加容易和快捷,而这些应用程序能够与各种Web服务器,应用服务器,浏览器和开发工具共同工作。 Web应用开发的JavaServer Pages技术方法 在开发JSP规范的过程中,太阳微系统公司(Sun Microsystems Inc.)与许许多多主要的Web服务器、应用服务器和开发工具供应商,以及各种各样富有经验的开发团体进行合作。其结果是找到了一种为应用和页面开发人员平衡了可移植性和易用性的开发方法。 JSP技术在多个方面加速了动态Web页面的开发: 将内容的生成和显示进行分离 使用JSP技术,Web页面开发人员可以使用HTML或者XML标识来设计和格式化最终页面。使用JSP标识或者小脚本来生成页面上的动态内容(内容是根据请求来变化的,例如请求帐户信息或者特定的一瓶酒的价格)。生成内容的逻辑被封装在标识和JavaBeans组件中,并且捆绑在小脚本中,所有的脚本在服务器端运行。如果核心逻辑被封装在标识和Beans中,那么其他人,如Web管理人员和页面设计者,能够编辑和使用JSP页面,而不影响内容的生成。 在服务器端,JSP引擎解释JSP标识和小脚本,生成所请求的内容(例如,通过访问JavaBeans组件,使用JDBCTM技术访问数据库,或者包含文件),并且将结果以HTML(或者XML)页面的形式发送回浏览器。这有助于作者保护自己的代码,而又保证任何基于HTML的Web浏览器的完全可用性。 强调可重用的组件 绝大多数JSP页面依赖于可重用的,跨平台的组件(JavaBeans或者Enterprise JavaBeansTM组件)来执行应用程序所要求的更为复杂的处理。开发人员能够共享和交换执行普通操作的组件,或者使得这些组件为更多的使用者或者客户团体所使用。基于组件的方法加速了总体开发过程,并且使得各种组织在他们现有的技能和优化结果的开发努力中得到平衡。 采用标识简化页面开发 Web页面开发人员不会都是熟悉脚本语言的编程人员。JavaServer Page技术封装了许多功能,这些功能是在易用的、与JSP相关的XML标识中进行动态内容生成所需要的。标准的JSP标识能够访问和实例化JavaBeans组件,设置或者检索组件属性,下载Applet,以及执行用其他方法更难于编码和耗时的功能。 通过开发定制化标识库,JSP技术是可以扩展的。今后,第三方开发人员和其他人员可以为常用功能创建自己的标识库。这使得Web页面开发人员能够使用熟悉的工具和如同标识一样的执行特定功能的构件来工作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值