标签来实现上传的情况,存在着严重的性能问题,因为用户提交了文件之后,在浏览器把文件上传到服务器的过程中,界面看上去似乎是静止的,如果是小文件还好些,如果不幸需要上传的是几兆、几十兆甚至上百兆的文件,我相信那是一种非常痛苦的体验,我们中间的很多人应该都有过此种不堪的经历。(一笑)现在我就针对这个问题给出一个解决方案,我们将实现一个具有监控能力的WEB上传的程序——它不仅把文件上传到服务器,而且\"实时地\"监视文件上传的实际过程。解决方案的基本思路是这样的:
••••
在Form提交上传文件同时,使用AJAX周期性地从Servlet轮询上传状态信息然后,根据此信息更新进度条和相关文字,及时反映文件传输状态
如果用户取消上传操作,则进行相应的现场清理工作:删除已经上传的文件,在Form提交页面中显示相关信息
如果上传完毕,显示已经上传的文件内容(或链接)
在介绍源代码之前,我们先来看看程序运行界面:
2.实现代码
实现代码想当然的有服务器端代码和客户端代码(呵呵),我们先从服务器端开始。2.1.服务器端代码
2.1.1.文件上传状态类(FileUploadStatus)
使用FileUploadStatus这个类记录文件上传状态,并将其作为服务器端与web客户端之间通信的媒介,通过对这个类对象提供上传状态作为服务器回应发送给web客户端,web客户端使用JavaScript获得文件上传状态。源代码如下:/***本例程演示了通过Web上传文件过程中的进度显示。您可以对本例程进行任何修改和使用。IT教程网-Java视频教程http://www.itjiaocheng.com
**如需要转载,请注明作者。**作者:刘作晨**/packageliuzuochen.sample.upload;importjava.util.*;publicclassFileUploadStatus{//上传用户地址privateStringuploadAddr;//上传总量privatelonguploadTotalSize=0;//读取上传总量privatelongreadTotalSize=0;//当前上传文件号privateintcurrentUploadFileNum=0;//成功读取上传文件数privateintsuccessUploadFileCount=0;//状态privateStringstatus=\"\";//处理起始时间privatelongprocessStartTime=0l;//处理终止时间privatelongprocessEndTime=0l;//处理执行时间privatelongprocessRunningTime=0l;//上传文件URL列表privateListuploadFileUrlList=newArrayList();//取消上传privatebooleancancel=false;//上传base目录privateStringbaseDir=\"\";publicFileUploadStatus(){}publicStringgetBaseDir(){returnbaseDir;}publicvoidsetBaseDir(StringbaseDir){IT教程网-Java视频教程http://www.itjiaocheng.com
this.baseDir=baseDir;}publicbooleangetCancel(){returncancel;}publicvoidsetCancel(booleancancel){this.cancel=cancel;}publicListgetUploadFileUrlList(){returnuploadFileUrlList;}publicvoidsetUploadFileUrlList(ListuploadFileUrlList){this.uploadFileUrlList=uploadFileUrlList;}publiclonggetProcessRunningTime(){returnprocessRunningTime;}publicvoidsetProcessRunningTime(longprocessRunningTime){this.processRunningTime=processRunningTime;}publiclonggetProcessEndTime(){returnprocessEndTime;}publicvoidsetProcessEndTime(longprocessEndTime){this.processEndTime=processEndTime;}publiclonggetProcessStartTime(){returnprocessStartTime;}publicvoidsetProcessStartTime(longprocessStartTime){this.processStartTime=processStartTime;}publiclonggetReadTotalSize(){returnreadTotalSize;IT教程网-Java视频教程http://www.itjiaocheng.com
}publicvoidsetReadTotalSize(longreadTotalSize){this.readTotalSize=readTotalSize;}publicintgetSuccessUploadFileCount(){returnsuccessUploadFileCount;}publicvoidsetSuccessUploadFileCount(intsuccessUploadFileCount){this.successUploadFileCount=successUploadFileCount;}publicintgetCurrentUploadFileNum(){returncurrentUploadFileNum;}publicvoidsetCurrentUploadFileNum(intcurrentUploadFileNum){this.currentUploadFileNum=currentUploadFileNum;}publicStringgetStatus(){returnstatus;}publicvoidsetStatus(Stringstatus){this.status=status;}publiclonggetUploadTotalSize(){returnuploadTotalSize;}publicStringgetUploadAddr(){returnuploadAddr;}publicvoidsetUploadTotalSize(longuploadTotalSize){this.uploadTotalSize=uploadTotalSize;}publicvoidsetUploadAddr(StringuploadAddr){this.uploadAddr=uploadAddr;IT教程网-Java视频教程http://www.itjiaocheng.com
}publicStringtoJSon(){StringBufferstrJSon=newStringBuffer();strJSon.append(\"{UploadTotalSize:\").append(getUploadTotalSize()).append(\.append(\"ReadTotalSize:\").append(getReadTotalSize()).append(\.append(\"CurrentUploadFileNum:\").append(getCurrentUploadFileNum()).append(\.append(\"SuccessUploadFileCount:\").append(getSuccessUploadFileCount()).append(\.append(\"Status:'\").append(getStatus()).append(\"',\").append(\"ProcessStartTime:\").append(getProcessStartTime()).append(\.append(\"ProcessEndTime:\").append(getProcessEndTime()).append(\.append(\"ProcessRunningTime:\").append(getProcessRunningTime()).append(\.append(\"Cancel:\").append(getCancel()).append(\returnstrJSon.toString();}}2.1.2.文件上传状态侦听类(FileUploadListener)
使用Common-FileUpload1.2版本(20070103)。此版本提供了能够监视文件上传情况的ProcessListener接口,使开发者通过FileUploadBase类对象的setProcessListener方法植入自己的Listener。FileUploadListener类实现了ProcessListener,在整个文件上传过程中,它对上传进度进行监控,并且根据上传情况实时的更新上传状态Bean。源代码如下:**/packageliuzuochen.sample.upload;importorg.apache.commons.fileupload.ProgressListener;importjavax.servlet.http.HttpServletRequest;publicclassFileUploadListenerimplementsProgressListener{privateHttpServletRequestrequest=null;publicFileUploadListener(HttpServletRequestrequest){this.request=request;IT教程网-Java视频教程http://www.itjiaocheng.com
}/***更新状态*/publicvoidupdate(longpBytesRead,longpContentLength,intpItems){FileUploadStatusstatusBean=BackGroundService.getStatusBean(request);statusBean.setUploadTotalSize(pContentLength);//读取完成if(pContentLength==-1){statusBean.setStatus(\"完成对\"+pItems+\"个文件的读取:读取了\"+pBytesRead+\"bytes.\");statusBean.setReadTotalSize(pBytesRead);statusBean.setSuccessUploadFileCount(pItems);statusBean.setProcessEndTime(System.currentTimeMillis());statusBean.setProcessRunningTime(statusBean.getProcessEndTime());//读取中}else{statusBean.setStatus(\"当前正在处理第\"+pItems+\"个文件:已经读取了\"+pBytesRead+\"/\"+pContentLength+\"bytes.\");statusBean.setReadTotalSize(pBytesRead);statusBean.setCurrentUploadFileNum(pItems);statusBean.setProcessRunningTime(System.currentTimeMillis());}2.1.3.后台服务类(BackGroundService)
BackGroundService这个Servlet类负责接收FormPost数据、回应状态轮询请求、处理取消文件上传的请求。尽管可以把这些功能相互分离开来,但为了简单明了,还是将它们放到Servlet中,只是由不同的方法进行分割。源代码如下:
IT教程网-Java视频教程http://www.itjiaocheng.com
**/packageliuzuochen.sample.upload;importjava.io.File;importjava.io.IOException;importjava.util.List;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importorg.apache.commons.fileupload.FileItem;importorg.apache.commons.fileupload.FileUploadException;importorg.apache.commons.fileupload.disk.DiskFileItemFactory;importorg.apache.commons.fileupload.servlet.*;publicclassBackGroundServiceextendsjavax.servlet.http.HttpServletimplementsjavax.servlet.Servlet{publicstaticfinalStringUPLOAD_DIR=\"/upload\";publicstaticfinalStringDEFAULT_UPLOAD_FAILURE_URL=\"./result.jsp\";publicBackGroundService(){super();}protectedvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{doPost(request,response);}/***从文件路径中取出文件名*/privateStringtakeOutFileName(StringfilePath){intpos=filePath.lastIndexOf(File.separator);if(pos>0){returnfilePath.substring(pos+1);}else{returnfilePath;IT教程网-Java视频教程http://www.itjiaocheng.com
}}/***从request中取出FileUploadStatusBean*/publicstaticFileUploadStatusgetStatusBean(HttpServletRequestrequest){BeanControlerbeanCtrl=BeanControler.getInstance();returnbeanCtrl.getUploadStatus(request.getRemoteAddr());}/***把FileUploadStatusBean保存到类控制器BeanControler*/publicstaticvoidsaveStatusBean(HttpServletRequestrequest,FileUploadStatusstatusBean){statusBean.setUploadAddr(request.getRemoteAddr());BeanControlerbeanCtrl=BeanControler.getInstance();beanCtrl.setUploadStatus(statusBean);}/***删除已经上传的文件*/privatevoiddeleteUploadedFile(HttpServletRequestrequest){FileUploadStatussatusBean=getStatusBean(request);for(inti=0;iStringerrMsg)throwsServletException,IOException{//首先删除已经上传的文件deleteUploadedFile(request);FileUploadStatussatusBean=getStatusBean(request);satusBean.setStatus(errMsg);saveStatusBean(request,satusBean);}/***初始化文件上传状态Bean*/privateFileUploadStatusinitStatusBean(HttpServletRequestrequest){FileUploadStatussatusBean=newFileUploadStatus();satusBean.setStatus(\"正在准备处理\");satusBean.setUploadTotalSize(request.getContentLength());satusBean.setProcessStartTime(System.currentTimeMillis());satusBean.setBaseDir(request.getContextPath()+UPLOAD_DIR);returnsatusBean;}/***处理文件上传*/privatevoidprocessFileUpload(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{DiskFileItemFactoryfactory=newDiskFileItemFactory();//设置内存缓冲区,超过后写入临时文件factory.setSizeThreshold(10240000);//设置临时文件存储位置factory.setRepository(newFile(request.getRealPath(\"/upload/temp\")));ServletFileUploadupload=newServletFileUpload(factory);//设置单个文件的最大上传值upload.setFileSizeMax(102400000);//设置整个request的最大值upload.setSizeMax(102400000);upload.setProgressListener(newFileUploadListener(request));//保存初始化后的FileUploadStatusBeansaveStatusBean(request,initStatusBean(request));StringforwardURL=\"\";try{Listitems=upload.parseRequest(request);IT教程网-Java视频教程http://www.itjiaocheng.com//获得返回urlfor(inti=0;i0){StringfileName=takeOutFileName(item.getName());FileuploadedFile=newFile(request.getRealPath(UPLOAD_DIR)+File.separator+fileName);item.write(uploadedFile);//更新上传文件列表FileUploadStatussatusBean=getStatusBean(request);satusBean.getUploadFileUrlList().add(fileName);saveStatusBean(request,satusBean);Thread.sleep(500);}}}catch(FileUploadExceptione){uploadExceptionHandle(request,\"上传文件时发生错误:\"+e.getMessage());}catch(Exceptione){uploadExceptionHandle(request,\"保存上传文件时发生错误:\"+e.getMessage());}if(forwardURL.length()==0){forwardURL=DEFAULT_UPLOAD_FAILURE_URL;}request.getRequestDispatcher(forwardURL).forward(request,response);}/**IT教程网-Java视频教程http://www.itjiaocheng.com*回应上传状态查询*/privatevoidresponseStatusQuery(HttpServletRequestrequest,HttpServletResponseresponse)throwsIOException{response.setContentType(\"text/xml\");response.setCharacterEncoding(\"UTF-8\");response.setHeader(\"Cache-Control\\"no-cache\");FileUploadStatussatusBean=getStatusBean(request);response.getWriter().write(satusBean.toJSon());}/***处理取消文件上传*/privatevoidprocessCancelFileUpload(HttpServletRequestrequest,HttpServletResponseresponse)throwsIOException{FileUploadStatussatusBean=getStatusBean(request);satusBean.setCancel(true);saveStatusBean(request,satusBean);responseStatusQuery(request,response);}protectedvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{booleanisMultipart=ServletFileUpload.isMultipartContent(request);if(isMultipart){processFileUpload(request,response);}else{request.setCharacterEncoding(\"UTF-8\");if(request.getParameter(\"uploadStatus\")!=null){responseStatusQuery(request,response);}if(request.getParameter(\"cancelUpload\")!=null){processCancelFileUpload(request,response);}}}}IT教程网-Java视频教程http://www.itjiaocheng.com
2.1.4.文件上传状态控制类(BeanControler)
这是一个单例类,它的功能是为客户端保存文件上传状态,这里我没有使用Session来存储文件上传状态,因为对于AJAX这种异步调用,服务器会开启不同的Session,所以无法通过Session保存文件上传状态。我并不认为这种方法最好,如果有更好的方法,欢迎大家一起讨论。源代码如下:**/packageliuzuochen.sample.upload;importjava.util.Vector;publicclassBeanControler{privatestaticBeanControlerbeanControler=newBeanControler();privateVectorvector=newVector();privateBeanControler(){}publicstaticBeanControlergetInstance(){returnbeanControler;}/***取得相应FileUploadStatus类对象的存储位置*/privateintindexOf(StringstrID){intnReturn=-1;for(inti=0;ipublicvoidsetUploadStatus(FileUploadStatusstatus){intnIndex=indexOf(status.getUploadAddr());if(-1==nIndex){vector.add(status);}else{vector.insertElementAt(status,nIndex);vector.removeElementAt(nIndex+1);}}/***删除FileUploadStatus类对象*/publicvoidremoveUploadStatus(StringstrID){intnIndex=indexOf(strID);if(-1!=nIndex)vector.removeElementAt(nIndex);}}2.2.客户端代码客户端我们采用Prototype框架。2.2.1.AjaxWrapper.js
AjaxWrapper.js对Prototype进行了封装。源代码如下://类工具varClassUtils=Class.create();ClassUtils.prototype={_ClassUtilsName:'ClassUtils',initialize:function(){},/***给类的每个方法注册一个对类对象的自我引用*@paramreference对类对象的引用*/registerFuncSelfLink:function(reference){for(varninreference){varitem=reference[n];if(iteminstanceofFunction)item.$=reference;}}IT教程网-Java视频教程http://www.itjiaocheng.com
}//Ajax操作封装类://由于调用AjaxRequest类进行XMLHTTPRequest操作时,this引用(指向当前的对象)会出现了callstack问题,从而指向当前的对象。//所以,对putRequest、callBackHandler、以及callback方法都要使用arguments.callee.$来获得正确的类对象引用varAjaxWrapper=Class.create();AjaxWrapper.prototype={debug_flag:false,xml_source:'',/***初始化*@paramisDebug是否显示调试信息*/initialize:function(isDebug){newClassUtils().registerFuncSelfLink(this);this.debug_flag=isDebug;},/***以get的方式向server发送request*@paramurl*@paramparams*@paramcallBackFunction发送成功后回调的函数或者函数名*/putRequest:function(url,params,callBackFunction){varfuncHolder=arguments.callee.$;varxmlHttp=newAjax.Request(url,{method:'get',parameters:params,requestHeaders:['my-header-encoding','utf-8'],onFailure:function(){alert('对不起,网络通讯失败,请重新刷新!');},onSuccess:function(transport){},onComplete:function(transport){funcHolder.callBackHandler.apply(funcHolder,[transport,callBackFunction]);}});},/***以post的方式向server发送xml请求*@paramurlIT教程网-Java视频教程http://www.itjiaocheng.com
*@parampostDataBody*@paramcallBackFunction发送成功后回调的函数或者函数名*/pushRequest:function(url,postDataBody,callBackFunction){varfuncHolder=arguments.callee.$;varoptions={method:'post',parameters:'',requestHeaders:['my-header-encoding','utf-8'],postBody:postDataBody,onFailure:function(transport){alert('对不起,网络通讯失败,请重新发送!');},onComplete:function(transport){funcHolder.callBackHandler.apply(funcHolder,[transport,callBackFunction]);}};varxmlHttp=newAjax.Request(url,options);},/***远程调用的回调处理*@paramtransportxmlhttp的transport*@paramcallBackFunction回调时call的方法,可以是函数也可以是函数名*/callBackHandler:function(transport,callBackFunction){varfuncHolder=arguments.callee.$;if(transport.status!=200){alert(\"获得回应失败,请求状态:\"+transport.status);}else{funcHolder.xml_source=transport.responseText;if(funcHolder.debug_flag)alert('callcallbackfunction');if(typeof(callBackFunction)=='function'){if(funcHolder.debug_flag){alert('invokecallbackFunc');}callBackFunction(transport.responseText);}else{if(funcHolder.debug_flag){alert('evalFunccallbackFunc');}newexecute().evalFunc(callBackFunction,transport.responseText);IT教程网-Java视频教程http://www.itjiaocheng.com
}if(funcHolder.debug_flag)alert('endcallbackfunction');}},//显示xml信息showXMLResponse:function(){varfuncHolder=arguments.callee.$;alert(funcHolder.xml_source);}}varXMLDomForAjax=Class.create();XMLDomForAjax.prototype={isDebug:false,//dom节点类型常量ELEMENT_NODE:1,ATTRIBUTE_NODE:2,TEXT_NODE:3,CDATA_SECTION_NODE:4,ENTITY_REFERENCE_NODE:5,ENTITY_NODE:6,PROCESSING_INSTRUCTION_NODE:7,COMMENT_NODE:8,DOCUMENT_NODE:9,DOCUMENT_TYPE_NODE:10,DOCUMENT_FRAGMENT_NODE:11,NOTATION_NODE:12,initialize:function(isDebug){newClassUtils().registerFuncSelfLink(this);this.isDebug=isDebug;},/***建立跨平台的dom解析器*@paramxmlxml字符串*@returndom解析器*/createDomParser:function(xml){//codeforIEif(window.ActiveXObject){vardoc=newActiveXObject(\"Microsoft.XMLDOM\");doc.async=\"false\";doc.loadXML(xml);}//codeforMozilla,Firefox,Opera,etc.IT教程网-Java视频教程http://www.itjiaocheng.com
else{varparser=newDOMParser();vardoc=parser.parseFromString(xml,\"text/xml\");}returndoc;},/***反向序列化xml到javascriptBean*@paramxmlxml字符串*@returnjavascriptBean*/deserializedBeanFromXML:function(xml){varfuncHolder=arguments.callee.$;vardoc=funcHolder.createDomParser(xml);//documentElement总表示文档的rootvarobjDomTree=doc.documentElement;varobj=newObject();for(vari=0;i0){objFieldValue[objFieldValue.length]=nodeText;}}else{objFieldValue=newArray();}}elseif(node.getAttribute('type')=='long'||node.getAttribute('type')=='java.lang.Long'||node.getAttribute('type')=='int'||node.getAttribute('type')=='java.lang.Integer'){objFieldValue=parseInt(nodeText);}IT教程网-Java视频教程http://www.itjiaocheng.comelseif(node.getAttribute('type')=='double'||node.getAttribute('type')=='float'||node.getAttribute('type')=='java.lang.Double'||node.getAttribute('type')=='java.lang.Float'){objFieldValue=parseFloat(nodeText);}elseif(node.getAttribute('type')=='java.lang.String'){objFieldValue=nodeText;}else{objFieldValue=nodeText;}//赋值给对象obj[node.getAttribute('name')]=objFieldValue;if(funcHolder.isDebug){alert(eval('obj.'+node.getAttribute('name')));}}elseif(node.nodeType==funcHolder.TEXT_NODE){if(funcHolder.isDebug){//alert('TEXT_NODE');}}elseif(node.nodeType==funcHolder.CDATA_SECTION_NODE){if(funcHolder.isDebug){//alert('CDATA_SECTION_NODE');}}}returnobj;},/***获得dom节点的text*/getNodeText:function(node){varfuncHolder=arguments.callee.$;//isthisatextorCDATAnode?if(node.nodeType==funcHolder.TEXT_NODE||node.nodeType==funcHolder.CDATA_SECTION_NODE){returnnode.data;}vari;varreturnValue=[];for(i=0;i//采用递归算法returnValue.push(funcHolder.getNodeText(node.childNodes[i]));}returnreturnValue.join('');}}//委托者类varDispatcher=Class.create();Dispatcher.prototype={name:'Dispatcher',//对class中的每个function都赋值一个值为this的$属性initialize:function(){newClassUtils().registerFuncSelfLink(this);},/***委托调用*@paramcaller调用者,func的拥有者*@paramfunc如果是function对象,则使用Dispatcher对象自己的name作为参数;否则直接调用func*/dispatch:function(caller,func){if(funcinstanceofFunction){varfuncArguments=newArray();funcArguments[0]=arguments.callee.$.name;func.apply(caller,funcArguments);}else{eval(func);}}}//祈祷者类varInvoker=Class.create();Invoker.prototype={name:'Invoker',initialize:function(){},invoke:function(showMsg){alert(showMsg+\"——this.name=\"+this.name);}}IT教程网-Java视频教程http://www.itjiaocheng.com2.2.2.fileUpload.html
fileUpload.html是文件上传界面。源代码如下:
文件上传测试说明:最大上传量:100M,单个文件最大长度:100M