以前项目里面都经常用到上传的功能,从最早的smartFileUpload到apache 的common-fileupload.但是运用归运用,还是要了解实质才行,下面以common-fileupload为例子来讲解
首先要了解上传的本质,首先上传需要在jsp页面的form标签中配置enctype="multipart/form-data"
因为这样配置后,在http请求发出时才会以2进制的方式去传输上传文件
当请求到达服务端后,在action我们的代码是
DiskFileItemFactory factory = new DiskFileItemFactory();
// 当文件大小超过300k时,就在磁盘上建立临时文件
factory.setSizeThreshold(300000);
//设计文件上传的临时目录
factory.setRepository(new File("D:\\temp"));
ServletFileUpload upload = new ServletFileUpload(factory);
// 文件大小不能超过20M
upload.setSizeMax(20000000);
用struts2做上传的朋友都知道,在struts2的property文件中可以配置上传文件大小和上传的临时文件目录,其实struts2也是这样的实现
在这里我们同时还可以使用upload.setProgressListener(new ProgressListener())来监控上传的进度,一般情况下都是采用ajax来同步客户端和服务端的数据
具体可以参考:http://www.ibm.com/developerworks/cn/opensource/os-cn-agajax/
在设定好了一系列参数后,我们就要开始主要的方法了
ServletFileUpload.parseRequest(Request request)
public List /* FileItem */ parseRequest(HttpServletRequest request)
throws FileUploadException {
return parseRequest(new ServletRequestContext(request));
}
public List /* FileItem */ parseRequest(RequestContext ctx)
throws FileUploadException {
try {
FileItemIterator iter = getItemIterator(ctx);//这个方法是相当重要的,在下面介绍
List items = new ArrayList();
FileItemFactory fac = getFileItemFactory();
if (fac == null) {
throw new NullPointerException(
"No FileItemFactory has been set.");
}
while (iter.hasNext()) {
FileItemStream item = iter.next();
FileItem fileItem = fac.createItem(item.getFieldName(),
item.getContentType(), item.isFormField(),
item.getName());
try {
//将2个流copy,item.openStream()是request中的流,fileItem.getOutputStream()是准备向指定上传文件夹写文件的流
//fileItem.getOutputStream() 中就设定了临时文件的路径 下面将介绍
Streams.copy(item.openStream(), fileItem.getOutputStream(),
true);
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new IOFileUploadException(
"Processing of " + MULTIPART_FORM_DATA
+ " request failed. " + e.getMessage(), e);
}
if (fileItem instanceof FileItemHeadersSupport) {
final FileItemHeaders fih = item.getHeaders();
((FileItemHeadersSupport) fileItem).setHeaders(fih);
}
items.add(fileItem);
}
//返回FileItem的集合(关于FileItem的实现类,大家可以参考DiskFileItem)
return items;
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new FileUploadException(e.getMessage(), e);
}
}
public OutputStream getOutputStream()
throws IOException {
if (dfos == null) {
File outputFile = getTempFile();
dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
}
return dfos;
}
protected File getTempFile() {
//如果没设置临时目录,就用System.getProperty("java.io.tmpdir")
if (tempFile == null) {
File tempDir = repository;
if (tempDir == null) {
tempDir = new File(System.getProperty("java.io.tmpdir"));
}
//设计临时文件名
String tempFileName =
"upload_" + UID + "_" + getUniqueId() + ".tmp";
tempFile = new File(tempDir, tempFileName);
}
return tempFile;
}
public FileItemIterator getItemIterator(RequestContext ctx)
throws FileUploadException, IOException {
return new FileItemIteratorImpl(ctx);
}
FileItemIteratorImpl(RequestContext ctx)
throws FileUploadException, IOException {
if (ctx == null) {
throw new NullPointerException("ctx parameter");
}
String contentType = ctx.getContentType();
//这就是为什么需要将请求写成multipart/form-data
// public static final String MULTIPART = "multipart/"
if ((null == contentType)
|| (!contentType.toLowerCase().startsWith(MULTIPART))) {
throw new InvalidContentTypeException(
"the request doesn't contain a " + MULTIPART_FORM_DATA + " or " + MULTIPART_MIXED + " stream, content type header is "+ contentType);
}
InputStream input = ctx.getInputStream();
//此处的sizeMax就是我们在action中set进去的值
if (sizeMax >= 0) {
int requestSize = ctx.getContentLength();
if (requestSize == -1) {
input = new LimitedInputStream(input, sizeMax) {
protected void raiseError(long pSizeMax, long pCount)
throws IOException {
FileUploadException ex =
new SizeLimitExceededException(
"the request was rejected because" + " its size (" + pCount + ") exceeds the configured maximum" + " (" + pSizeMax + ")", pCount, pSizeMax);
throw new FileUploadIOException(ex);
}
};
} else {
//此处主要是判断上传文件的大小是否超过了设定值
if (sizeMax >= 0 && requestSize > sizeMax) {
throw new SizeLimitExceededException(
"the request was rejected because its size ("
+ requestSize + ") exceeds the configured maximum ("+ sizeMax + ")", requestSize, sizeMax);
}
}
}
当返回了这个FileItem集合后,我们的代码如下
List<FileItem> items = getUploadFileIteams(request,pushObj,goUrl);
for (FileItem item : items) {
File file = new File(realPath);
item.write(file);
}
这里就将文件真正写到了服务器上
再最后把item.write这个方法贴下吧,大家就更明白了
public void write(File file) throws Exception {
if (isInMemory()) {
FileOutputStream fout = null;
try {
fout = new FileOutputStream(file);
fout.write(get());
} finally {
if (fout != null) {
fout.close();
}
}
} else {
File outputFile = getStoreLocation();
if (outputFile != null) {
// Save the length of the file
size = outputFile.length();
//看到下面的代码,大家就应该明白了吧
if (!outputFile.renameTo(file)) {
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
in = new BufferedInputStream(
new FileInputStream(outputFile));
out = new BufferedOutputStream(
new FileOutputStream(file));
IOUtils.copy(in, out);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
// ignore
}
}
}
}
} else {
/*
* For whatever reason we cannot write the
* file to disk.
*/
throw new FileUploadException(
"Cannot write uploaded file to disk!");
}
}
}
关于临时文件的删除,我们可以考虑2种方式,利用DiskFileItem#delete方法手动删除,也可以用
<listener>
<listener-class>org.apache.commons.fileupload.servlet.FileCleanerCleanup</listener-class>
</listener>
关于FileCleanerCleanup的执行大家可以看看org.apache.commons.io.FileCleaningTracker的实现