先看效果:
在struts2中上傳是很簡單的,struts2會先把文件寫到臨時文件中,以後在提供這個文件的File對象到action中。具體原理看這裏:
http://blog.csdn.net/tom_221x/archive/2009/01/12/3761390.aspx。
利用servlet和common-upload.jar很容易實現顯示文件上傳的進度,在common-upload組件中實現一個ProgressListener接口,組件會把上傳的實時進度傳給你。但是想在struts2中,實時的顯示進度是有些困難的。因爲struts2把request對象給封裝了,在Action中拿到request對象,如果是上傳文件,那麼struts2已經把文件寫到文件系統裏去了。
struts2上傳文件的時候封裝request對象其實是org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper,也就是說在action中拿到的實際類型是MultiPartRequestWrapper。片段如下:
- public class MultiPartRequestWrapper extends StrutsRequestWrapper {
- protected static final Logger LOG = LoggerFactory.getLogger(MultiPartRequestWrapper.class);
- Collection<String> errors;
- MultiPartRequest multi;
- /**
- * Process file downloads and log any errors.
- *
- * @param request Our HttpServletRequest object
- * @param saveDir Target directory for any files that we save
- * @param multiPartRequest Our MultiPartRequest object
- */
- public MultiPartRequestWrapper(MultiPartRequest multiPartRequest, HttpServletRequest request, String saveDir) {
- super(request);
- multi = multiPartRequest;
- try {
- multi.parse(request, saveDir);
- for (Object o : multi.getErrors()) {
- String error = (String) o;
- addError(error);
- }
- } catch (IOException e) {
- addError("Cannot parse request: "+e.toString());
- }
- }
可以看到在構造的時候,調用multi.parse(request, saveDir)把上傳的數據給封裝了。這個MultiPartRequest的解析功能是在struts-default.xml中配置的,如下:
- <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="struts" class="org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest" scope="default"/>
- !-- 文件解析器類 -->
- <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="jakarta" class="org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest" scope="default" />
- !-- 這就是struts2的文件解析器設置 -->
- <constant name="struts.multipart.handler" value="jakarta" />
現在的設想是,strut2不要幫我解析上傳的文件,留到action中,我自己設置。所以我們要覆蓋這是配置,如下:
- <!-- 重寫文件上傳解析方法 -->
- <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="myRequestParser"
- class="com.*.*.utils.MyRequestParseWrapper" scope="default" optional="true" />
- ;constant name="struts.multipart.handler" value="myRequestParser" />
這個MyRequestParseWrapper如下:
- /**
- * 重寫struts2的request封裝類
- *
- * @author scott.Cgi
- */
- public class MyRequestParseWrapper extends JakartaMultiPartRequest {
- /*
- * (non-Javadoc)
- * @see
- * org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest#parse
- * (javax.servlet.http.HttpServletRequest, java.lang.String)
- */
- @Override
- public void parse(HttpServletRequest servletRequest, String saveDir)
- throws IOException {
- //什麼也不做
- }
- }
這樣一來,在action中拿到的request就是帶有上傳文件的了。
接下來,所以說實現原理,依然使用common-uplaod.jar組件:
1. 頁面有2個iframe,一個上傳數據,一個顯示進度。
2. 當然有2個action,一個上傳數據,一個回寫進度。
3. 上傳的時候首先請求的是更新進度的iframe, 這個iframe執行客戶端js發起上傳文件請求,第二個iframe開始上傳數據。與此同時,第一個iframe開始回寫進度。進度對象保存在 session中,通過request的hashcode爲key。進度從第一個進度iframe,傳遞到第二個上傳iframe中,實現進度信息的通信。
說明一下,2個iframe是因爲,request未結束,不可以向客戶端寫數據,所以文件超大,就會阻塞回寫信息。
具體的上傳我封裝了一下,具體代碼如下:
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.PrintWriter;
- import java.util.List;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.apache.commons.fileupload.FileItem;
- import org.apache.commons.fileupload.ProgressListener;
- import org.apache.commons.fileupload.disk.DiskFileItemFactory;
- import org.apache.commons.fileupload.servlet.ServletFileUpload;
- import org.apache.log4j.Logger;
- /**
- * upload file
- *
- * @author scott.Cgi
- */
- public class UploadFile {
- private static final Logger LOG = Logger.getLogger(UploadFile.class);
- /**
- * 上傳文件
- *
- * @param request
- * http request
- * @param response
- * htp response
- * @throws IOException
- * IOException
- */
- @SuppressWarnings("unchecked")
- public static void upload(HttpServletRequest request,
- HttpServletResponse response) throws IOException {
- LOG.info("客戶端提交類型: " + request.getContentType());
- if (request.getContentType() == null) {
- throw new IOException(
- "the request doesn't contain a multipart/form-data stream");
- }
- String key = request.getParameter("key");
- Progress p = (Progress)request.getSession().getAttribute(key);
- // 設置上傳文件總大小
- p.setLength(request.getContentLength());
- LOG.info("上傳文件大小爲 : " + p.getLength());
- // 上傳臨時路徑
- String path = request.getSession().getServletContext().getRealPath("/");
- LOG.info("上傳臨時路徑 : " + path);
- // 設置上傳工廠
- DiskFileItemFactory factory = new DiskFileItemFactory();
- factory.setRepository(new File(path));
- // 閥值,超過這個值纔會寫到臨時目錄
- factory.setSizeThreshold(1024 * 1024 * 10);
- ServletFileUpload upload = new ServletFileUpload(factory);
- // 最大上傳限制
- upload.setSizeMax(1024 * 1024 * 200);
- // 設置監聽器監聽上傳進度
- upload.setProgressListener(p);
- try {
- LOG.info("解析上傳文件....");
- List<FileItem> items = upload.parseRequest(request);
- LOG.info("上傳數據...");
- for (FileItem item : items) {
- // 非表單域
- if (!item.isFormField()) {
- LOG.info("上傳路徑 : " + path + item.getName());
- FileOutputStream fos = new FileOutputStream(path + item.getName());
- // 文件全在內存中
- if (item.isInMemory()) {
- fos.write(item.get());
- p.setComplete(true);
- } else {
- InputStream is = item.getInputStream();
- byte[] buffer = new byte[1024];
- int len;
- while ((len = is.read(buffer)) > 0) {
- fos.write(buffer, 0, len);
- }
- is.close();
- }
- fos.close();
- LOG.info("完成上傳文件!");
- item.delete();
- LOG.info("刪除臨時文件!");
- p.setComplete(true);
- LOG.info("更新progress對象狀態爲完成狀態!");
- }
- }
- } catch (Exception e) {
- LOG.error("上傳文件出現異常, 錯誤原因 : " + e.getMessage());
- // 發生錯誤,進度信息對象設置爲完成狀態
- p.setComplete(true);
- request.getSession().removeAttribute(key);
- }
- }
- /**
- * 執行客戶端腳本
- *
- * @param response
- * http response
- * @param script
- * javscript string
- * @throws IOException
- * IOException
- */
- public static void execClientScript(HttpServletResponse resposne,
- String script) throws IOException {
- PrintWriter out = resposne.getWriter();
- out.println("<mce:script type='text/javascript'><!--
- " + script + "
- // --></mce:script>");
- // fix ie problem
- out.println("---------------------------------------------------");
- out.println("---------------------------------------------------");
- out.println("---------------------------------------------------");
- out.println("---------------------------------------------------");
- out.println("---------------------------------------------------");
- out.println("---------------------------------------------------");
- out.println("---------------------------------------------------");
- out.flush();
- }
- /**
- * 上傳文件進度信息
- *
- * @author wanglei
- * @version 0.1
- */
- public static class Progress implements ProgressListener {
- // 文件總長度
- private long length = 0;
- // 已上傳的文件長度
- private long currentLength = 0;
- // 上傳是否完成
- private boolean isComplete = false;
- /*
- * (non-Javadoc)
- * @see org.apache.commons.fileupload.ProgressListener#update(long,
- * long, int)
- */
- @Override
- public void update(long bytesRead, long contentLength, int items) {
- this.currentLength = bytesRead;
- }
- /**
- * the getter method of length
- *
- * @return the length
- */
- public long getLength() {
- return length;
- }
- /**
- * the getter method of currentLength
- *
- * @return the currentLength
- */
- public long getCurrentLength() {
- return currentLength;
- }
- /**
- * the getter method of isComplete
- *
- * @return the isComplete
- */
- public boolean isComplete() {
- return isComplete;
- }
- /**
- * the setter method of the length
- *
- * @param length
- * the length to set
- */
- public void setLength(long length) {
- this.length = length;
- }
- /**
- * the setter method of the currentLength
- *
- * @param currentLength
- * the currentLength to set
- */
- public void setCurrentLength(long currentLength) {
- this.currentLength = currentLength;
- }
- /**
- * the setter method of the isComplete
- *
- * @param isComplete
- * the isComplete to set
- */
- public void setComplete(boolean isComplete) {
- this.isComplete = isComplete;
- }
- }
- }
action代碼:
- import java.io.IOException;
- import com.ufinity.mars.utils.UploadFile;
- import com.ufinity.mars.utils.UploadFile.Progress;
- import com.ufinity.savor.service.FileService;
- /**
- * file action
- *
- * @author scott.Cgi
- */
- public class FileAction extends AbstractAction {
- /** {field's description} */
- private static final long serialVersionUID = 6649027352616232244L;
- private FileService fileService;
- /**
- * 上傳文件頁面
- *
- * @return page view
- */
- public String preupload() {
- return SUCCESS;
- }
- /**
- * 上傳文件
- *
- * @return page view
- */
- public String uploadfile() {
- try {
- UploadFile.upload(this.request, this.response);
- } catch (IOException e) {
- LOG.error("上傳文件發生異常,錯誤原因 : " + e.getMessage());
- }
- return null;
- }
- /**
- * 顯示上傳文件進度進度
- *
- * @return page view
- */
- public String progress() {
- String callback1 = this.request.getParameter("callback1");
- String callback2 = this.request.getParameter("callback2");
- // 緩存progress對象的key值
- String key = Integer.toString(request.hashCode());
- // 新建當前上傳文件的進度信息對象
- Progress p = new Progress();
- // 緩存progress對象
- this.request.getSession().setAttribute(key, p);
- response.setContentType("text/html;charset=UTF-8");
- response.setHeader("pragma", "no-cache");
- response.setHeader("cache-control", "no-cache");
- response.setHeader("expires", "0");
- try {
- UploadFile.execClientScript(response, callback1 + "(" + key + ")");
- long temp = 0l;
- while (!p.isComplete()) {
- if (temp != p.getCurrentLength()) {
- temp = p.getCurrentLength();
- // 向客戶端顯示進度
- UploadFile.execClientScript(response, callback2 + "("
- + p.getCurrentLength() + "," + p.getLength() + ")");
- } else {
- //LOG.info("progress的狀態 :" + p.isComplete());
- //LOG.info("progress上傳的數據量 :+ " + p.getCurrentLength());
- //上傳進度沒有變化時候,不向客戶端寫數據,寫數據過於頻繁會讓chrome沒響應
- Thread.sleep(300);
- }
- }
- } catch (Exception e) {
- LOG.error("調用客戶端腳本錯誤,原因 :" + e.getMessage());
- p.setComplete(true);
- }
- this.request.getSession().removeAttribute(key);
- LOG.info("刪除progress對象的session key");
- return null;
- }
- /**
- * the getter method of fileService
- *
- * @return the fileService
- */
- public FileService getFileService() {
- return fileService;
- }
- /**
- * the setter method of the fileService
- *
- * @param fileService
- * the fileService to set
- */
- public void setFileService(FileService fileService) {
- this.fileService = fileService;
- }
- }
頁面代碼:
- <mce:style type="text/css"><!--
- iframe{
- border:none;
- width:0;
- height:0;
- }
- #p_out{
- width:200px;
- height:12px;
- margin:10px 0 0 0;
- padding:1px;
- font-size:10px;
- border:solid #6b8e23 1px;
- }
- #p_in{
- width:0%;
- height:100%;
- background-color:#6b8e23;
- margin:0;
- padding:0;
- }
- #dis{
- margin:0;
- padding:0;
- text-align:center;
- font-size:12px;
- height:12px;
- width:200px;
- }
- --></mce:style><style type="text/css" mce_bogus="1"> iframe{
- border:none;
- width:0;
- height:0;
- }
- #p_out{
- width:200px;
- height:12px;
- margin:10px 0 0 0;
- padding:1px;
- font-size:10px;
- border:solid #6b8e23 1px;
- }
- #p_in{
- width:0%;
- height:100%;
- background-color:#6b8e23;
- margin:0;
- padding:0;
- }
- #dis{
- margin:0;
- padding:0;
- text-align:center;
- font-size:12px;
- height:12px;
- width:200px;
- }
- </style>
- </head>
- <body>
- <div class="main">
- <div class="top">
- <jsp:include page="/top.jsp" />
- </div>
- <div style="width: 250px; margin: 0 auto;">
- <div class="errorbox">
- <s:actionerror/>
- </div>
- <form id="uploadfile_form" name="uploadfile_form" enctype="multipart/form-data"
- method="post" target="uploadfile_iframe">
- <input type="file" name="file" />
- <br><br>
- <button onclick="progress()">提交</button>
- <div id="p_out"><div id="p_in"></div></div>
- <div id="dis"></div>
- </form>
- <iframe frameborder="0" id="uploadfile_iframe" name="uploadfile_iframe" src="javascript:void(0)" mce_src="javascript:void(0)"></iframe>
- <iframe frameborder="0" id="progress_iframe" name="progress_iframe" src="javascript:void(0)" mce_src="javascript:void(0)"></iframe>
- </div>
- </div>
- </body>
- <mce:script type="text/javascript"><!--
- //上傳文件
- function uploadFile(key){
- document.forms[0].action = 'uploadfile.action?callback=parent.upload&key='+key;
- document.forms[0].submit();
- document.getElementById('dis').innerHTML = "開始傳送數據...";
- }
- //獲取文件上傳進度
- function progress(){
- document.getElementById('progress_iframe').src = 'progress.action?callback1=parent.uploadFile&callback2=parent.upload';
- document.getElementById('dis').innerHTML = '初始化數據...';
- document.getElementById('p_in').style.width = "0%";
- }
- //更新進度
- function upload(len, total){
- document.getElementById('p_in').style.width = (Math.round(len/total*100))+'%';
- document.getElementById('dis').innerHTML = len + '/' + total + ' Byte';
- if(len === total) {
- document.getElementById('dis').innerHTML = "文件上傳完成!";
- }
- }
- // --></mce:script>
- </html>
注意: common-upload.jar依賴common-io.jar
最後,以前寫過一個servlet的上傳文件顯示進度(很久以前的事了,唉。。。),在這裏:
http://blog.csdn.net/tom_221x/archive/2009/01/14/3777064.aspx
有興趣的實現一個試試吧,還以加入多個文件的隊列上傳,或是多線程上傳,還可以加入斷點續傳。哈哈,實現了記得要開源噢!