本文主要從三個面向跟大家介紹webwork檔案上傳下載知識,包括以下三個面向:
1. 包裝 Request 請求
2. 取得檔案上傳的解析類別
3. 專案實戰配置與使用
Web上傳和下載應該是很普遍的一個需求,無論是小型網站還是大並發訪問的交易網站。 WebWork 當然也提供了很友好的攔截器來實現對文件的上傳,讓我們可以專注與業務邏輯的設計和實現,在實現上傳和下載時順便關注了下框架上傳下載的實現。
1. 包裝 Request 請求
•每次客戶端請求 Action 時,都會呼叫 WebWork 調度類別 ServletDispatcher.service()方法。
具體流程請參考:詳解Webwork中Action 呼叫的方法
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException { try { if (encoding != null) { try { request.setCharacterEncoding(encoding); } catch (Exception localException) { } } if (locale != null) { response.setLocale(locale); } if (this.paramsWorkaroundEnabled) { request.getParameter("foo"); } request = wrapRequest(request); //封装 request请求 serviceAction(request, response, getNameSpace(request), getActionName(request), getRequestMap(request), getParameterMap(request), getSessionMap(request), getApplicationMap()); } catch (IOException e) { String message = "Could not wrap servlet request with MultipartRequestWrapper!"; log.error(message, e); sendError(request, response, 500, new ServletException(message, e)); } }
先來看看 wrapRequest 方法做了什麼:
protected HttpServletRequest wrapRequest(HttpServletRequest request) throws IOException { if ((request instanceof MultiPartRequestWrapper)) { return request; } if (MultiPartRequest.isMultiPart(request)) { request = new MultiPartRequestWrapper(request, getSaveDir(), getMaxSize()); } return request; }
• 首先判斷了傳進來的 request 是不是已經被封裝好的 MultiPartRequestWrapper,如果是就直接回傳。
• 否則再判斷 request 的頭資訊裡面的 ContentType 是不是多型參數 (multipart/formdata)的請求,如果就把 request 包裝成 WebWork 自己的 MultiPartRequestWrapper 類型,該類型繼承HttpServletRequestWrapper 。
MultiPartRequest.isMultiPart() 方法實作如下:
public static boolean isMultiPart(HttpServletRequest request){ String content_type = request.getHeader("Content-Type"); return content_type != null && content_type.startsWith("multipart/form-data"); }
•在 webwork.properties 裡面設定檔的暫存目錄、還有最大上傳大小,其實就是在這個時候發揮作用的。
•建立 MultiPartRequestWrapper 物件的時候傳入的參數是由 getSaveDir() 和 getMaxSize() 方 法 取得 得 的 。
•在方法裡會尋找配置裡面的 webwork.multipart.saveDir、webwork.multipart.maxSize 屬性,找到則使用該屬性指定的臨時目錄和上傳的最大內容,沒找到就使用環境的臨時目錄。
具體實現如下:
protected String getSaveDir() { String saveDir = Configuration.getString("webwork.multipart.saveDir").trim(); if (saveDir.equals("")) { File tempdir = (File) getServletConfig().getServletContext().getAttribute("javax.servlet.context.tempdir"); log.info("Unable to find 'webwork.multipart.saveDir' property setting. Defaulting to javax.servlet.context.tempdir"); if (tempdir != null) { saveDir = tempdir.toString(); } } else { File multipartSaveDir = new File(saveDir); if (!multipartSaveDir.exists()) { multipartSaveDir.mkdir(); } } if (log.isDebugEnabled()) { log.debug("saveDir=" + saveDir); } return saveDir; }
2. 取得檔案上傳的解析類別
再來看一下 MultiPartRequestWrapper 的建構子使如何包裝 request 的:
public MultiPartRequestWrapper(HttpServletRequest request, String saveDir, int maxSize) throws IOException { super(request); if ((request instanceof MultiPartRequest)) { this.multi = ((MultiPartRequest) request); } else { String parser = ""; parser = Configuration.getString("webwork.multipart.parser"); if (parser.equals("")) { log.warn("Property webwork.multipart.parser not set. Using com.opensymphony.webwork.dispatcher.multipart.PellMultiPartRequest"); parser = "com.opensymphony.webwork.dispatcher.multipart.PellMultiPartRequest"; } else if (parser.equals("pell")) { parser = "com.opensymphony.webwork.dispatcher.multipart.PellMultiPartRequest"; } else if (parser.equals("cos")) { parser = "com.opensymphony.webwork.dispatcher.multipart.CosMultiPartRequest"; } else if (parser.equals("jakarta")) { parser = "com.opensymphony.webwork.dispatcher.multipart.JakartaMultiPartRequest"; } try { Class baseClazz = MultiPartRequest.class; Class clazz = Class.forName(parser); if (!baseClazz.isAssignableFrom(clazz)) { addError("Class '" + parser + "' does not extend MultiPartRequest"); return; } Constructor ctor = clazz.getDeclaredConstructor(new Class[] { Class.forName("javax.servlet.http.HttpServletRequest"), String.class, Integer.TYPE }); Object[] parms = { request, saveDir, new Integer(maxSize) }; this.multi = ((MultiPartRequest) ctor.newInstance(parms)); } catch (ClassNotFoundException e) { addError("Class: " + parser + " not found."); } catch (NoSuchMethodException e) { addError("Constructor error for " + parser + ": " + e); } catch (InstantiationException e) { addError("Error instantiating " + parser + ": " + e); } catch (IllegalAccessException e) { addError("Access errror for " + parser + ": " + e); } catch (InvocationTargetException e) { addError(e.getTargetException().toString()); } } }
• 首先它判斷了傳入的 request 是不是 MultiPartRequest 抽象類別的子類,如果是就直接把自身的 MultiPartRequest 類型的變數引用 request。
• 否則讀取 WebWork 設定 的webwork.multipart.parser 屬性,該屬性決定 Webwork 內部以什麼實作檔案上傳。 如果沒有指定,則預設使用 PellMultiPartRequest 的實作。
•找到配置的類別後,WebWork 透過 Java 反射建立該類別的實例。所有支援的類別都是從 MultiPartRequest 繼承而來,創建該實例後向上轉型,並賦予 MultiPartRequestWrapper 的成員multi。
• WebWork 的檔案上傳封裝了幾款通用的 FileUpload lib,並不是自己實作的。
•它包括了pell,cos,apache common 三種實現,WebWork 對這三個包進行封裝,提供了一 個通用的訪問接口 MultiPartRequest,細節實現分別是 PellMultiPartRequest、 CosMultiPartRequest 、JakartaMultiPartRequest 。
• jakarta 支援多個檔案使用同一個HTTP參數名稱。如果直接使用WebWork 的FileUpload 攔截器,建議使用pell,因為當你上傳中文檔案名稱的檔案的時候,只有pell 套件會正確的獲得中文檔案名稱,apache common會將檔案名稱改為xxx.tmp這樣的文件名,而cos會亂碼, 因此我們唯一的選擇只有pell。
•cos 的功能比較強大,WebWork 的封裝卻使它喪失了許多的功能,cos 需要設定 request 的character encoding。 WebWork的封裝沒有設置,所以就導致了cos的亂碼問題,當然如果你單獨 使用cos,則會避免此類問題。
3. 專案實戰配置與使用
• 設定檔
action 配置:
<action name="uploadAttach" class=".....attach.action.uploadAttach" caption="上传附件"> <result name="success" type="dispatcher"> <param name="location">/result.jsp</param> </result> <result name="error" type="dispatcher"> <param name="location">/result.jsp</param> </result> <interceptor-ref name="defaultStack" /> <interceptor-ref name="fileUploadStack" /> //webwok 上传所需要的拦截栈 </action> //拦截栈的定义 <interceptor-stack name="fileUploadStack"> <interceptor-ref name="fileUpload"/> <interceptor-ref name="params"/> </interceptor-stack> //拦截栈对应的拦截器 <interceptor name="fileUpload" class="com.opensymphony.webwork.interceptor.FileUploadInterceptor"/> <interceptor name="params" class="com.opensymphony.xwork.interceptor.ParametersInterceptor"/>
•前端使用比較穩定、功能比較強大的 Ajaxupload這裡就不多說了,有官方網址:jQuery AjaxUpload 上傳圖片代碼
•透過對 Webwork 上傳請求的封裝和解析類別的獲取,所有的前戲都已經準備妥當,上傳攔截器裡面具體實現如下:
public String intercept(ActionInvocation invocation) throws Exception {if (!(ServletActionContext.getRequest() instanceof MultiPartRequestWrapper)) { if (log.isDebugEnabled()) { log.debug("bypass " + invocation.getProxy().getNamespace() + "/" + invocation.getProxy().getActionName()); } return invocation.invoke(); } Action action = invocation.getAction(); ValidationAware validation = null; if ((action instanceof ValidationAware)) { validation = (ValidationAware) action; } MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) ServletActionContext.getRequest(); if (multiWrapper.hasErrors()) { Collection errors = multiWrapper.getErrors(); Iterator i = errors.iterator(); while (i.hasNext()) { String error = (String) i.next(); if (validation != null) { validation.addActionError(error); } log.error(error); } } Enumeration e = multiWrapper.getFileParameterNames(); while ((e != null) && (e.hasMoreElements())) { String inputName = (String) e.nextElement(); String[] contentType = multiWrapper.getContentTypes(inputName); String[] fileName = multiWrapper.getFileNames(inputName); File[] file = multiWrapper.getFiles(inputName); if (file != null) { for (int i = 0; i < file.length; i++) { log.info("file " + inputName + " " + contentType[i] + " " + fileName[i] + " " + file[i]); } } if (file == null) { if (validation != null) { validation.addFieldError(inputName, "Could not upload file(s). Perhaps it is too large?"); } log.error("Error uploading: " + fileName); } else { invocation.getInvocationContext().getParameters().put(inputName, file); invocation.getInvocationContext().getParameters().put(inputName + "ContentType", contentType); invocation.getInvocationContext().getParameters().put(inputName + "FileName", fileName); } } String result = invocation.invoke(); for (Enumeration e1 = multiWrapper.getFileParameterNames(); e1 != null && e1.hasMoreElements();) { String inputValue = (String) e1.nextElement(); File file[] = multiWrapper.getFiles(inputValue); for (int i = 0; i < file.length; i++) { File f = file[i]; log.info("removing file " + inputValue + " " + f); if (f != null && f.isFile()) f.delete(); } } return result; }
•首先判斷目前請求是否為 包含多媒體請求,如果是則記錄日誌,並執行 Action。
•接著判斷在檔案上傳過程 MultiPartRequestWrapper 是否含有錯誤,將錯誤訊息,傳回給客戶端,不在繼續呼叫 Action。
•如果上面的判斷條件都沒有進行,開始遍歷 MultiPartRequestWrapper 中的上傳檔案的參數,並將其中的檔案名稱、檔案內容類型放入Action 參數 map 中,供後面的業務類別進行操作。
•在WebWork 的fileupload 攔截器功能中,它提供的File只是一個臨時文件,Action 執行之後就會被自動刪除,你必須在Action中自己處理文件的儲存問題,或者寫到伺服器的某個目錄,或者儲存到資料庫中。如果你準備寫到伺服器的某個目錄下面的話,你必須自己面臨著處理檔案同名的問題,但是實際上cos套件已經提供了 檔案重名的自動重命名規則。
透過以上程式碼介紹了Webwork 實作檔案上傳下載的相關知識,希望對大家有幫助。