FileUploadInterceptor 該攔截器處於defaultStack第十的位置,看其名稱就知道是用於處理文件上傳的,對於文件上傳有一點大家應該要了解:struts2處理文件上傳內部使用的是commons-fileupload組件,當我們在form表單中把enctype屬性設置為"multipart/form-data"時表示這是一個文件上傳請求,當struts2接收到這樣一個請求後把請求包裝在一個MultiPartRequestWrapper對象中,而MultiPartRequestWrapper又包裝了JakartaMultiPartRequest,JakartaMultiPartRequest對象才是真正處理文件上傳的request對象。 下面說一下該攔截器中三個重要的屬性: 1.maximumSize -->允許上傳文件上最大大小,單位為byte(字節) 2.allowedTypesSet -->允許上傳文件類型集合 3.allowedExtensionsSet -->允許上傳文件擴展名集合 必須要這三個條件都符合,文件上傳才能成功。 下面是該攔截器的intercept方法: [java] public String intercept(ActionInvocation invocation) throws Exception { ActionContext ac = invocation.getInvocationContext();//獲取ActionContext對象 //獲取request對象 HttpServletRequest request = (HttpServletRequest) ac.get(ServletActionContext.HTTP_REQUEST); //判斷是否是文件上傳請求 if (!(request instanceof MultiPartRequestWrapper)) { if (LOG.isDebugEnabled()) { ActionProxy proxy = invocation.getProxy(); LOG.debug(getTextMessage("struts.messages.bypass.request", new Object[]{proxy.getNamespace(), proxy.getActionName()}, ac.getLocale())); } //如果不是則直接調用下一個攔截器,因為不需要處理文件上傳 return invocation.invoke(); } ValidationAware validation = null; Object action = invocation.getAction();//獲取當前Action對象 //判斷有無實現ValidationAware接口,當Action繼承自ActionSupport類時是實現了該接口的 if (action instanceof ValidationAware) { validation = (ValidationAware) action; } //前面說了文件上傳時request類型就是MultiPartRequestWrapper MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request; //判斷文件上傳是否有錯誤,也就是是否符合上面的三個上傳條件 if (multiWrapper.hasErrors()) { for (String error : multiWrapper.getErrors()) { if (validation != null) {//有錯誤則添加ActionError,注意validation就是當前Action對象 validation.addActionError(error); } LOG.warn(error); } } //獲取上傳文件參數名(input的name屬性值)枚舉,因為struts2支持一欠上傳多個文件 Enumeration fileParameterNames = multiWrapper.getFileParameterNames(); while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {//迭代 // 當前文件參數名 String inputName = (String) fileParameterNames.nextElement(); // 獲取上傳文件類型,因為多個文件上傳input的name屬性可以相同,所以這裡是數組 String[] contentType = multiWrapper.getContentTypes(inputName); if (isNonEmpty(contentType)) { // 獲取上傳文件的真實名稱(在客戶端文件系統中的名稱),至於返回值為什麼是數組與上同理 String[] fileName = multiWrapper.getFileNames(inputName); if (isNonEmpty(fileName)) { // 獲取上傳的文件 File[] files = multiWrapper.getFiles(inputName); if (files != null && files.length > 0) { List<File> acceptedFiles = new ArrayList<File>(files.length); List<String> acceptedContentTypes = new ArrayList<String>(files.length); List<String> acceptedFileNames = new ArrayList<String>(files.length); String contentTypeName = inputName + "ContentType"; String fileNameName = inputName + "FileName";//這時就是文件上傳時接收文件類型與文件名稱固定寫法的原因 //迭代文件數組 for (int index = 0; index < files.length; index++) { //判斷該文件是否合法,如果合法則添加到合法集合中 if (acceptFile(action, files[index], fileName[index], contentType[index], inputName, validation, ac.getLocale())) { acceptedFiles.add(files[index]); acceptedContentTypes.add(contentType[index]); acceptedFileNames.add(fileName[index]); } } //如果合法文件集合不為空 if (!acceptedFiles.isEmpty()) { Map<String, Object> params = ac.getParameters(); //將上傳的文件,文件類型,文件名稱存儲到ActionContext的parameters Map中 params.put(inputName, acceptedFiles.toArray(new File[acceptedFiles.size()])); params.put(contentTypeName, acceptedContentTypes.toArray(new String[acceptedContentTypes.size()])); params.put(fileNameName, acceptedFileNames.toArray(new String[acceptedFileNames.size()])); } } } else { LOG.warn(getTextMessage(action, "struts.messages.invalid.file", new Object[]{inputName}, ac.getLocale())); } } else { LOG.warn(getTextMessage(action, "struts.messages.invalid.content.type", new Object[]{inputName}, ac.getLocale())); } } // 調用下一個攔截器,當然也調用了Action String result = invocation.invoke(); // 當Action執行完畢,攔截器執行依次返回又執行到該攔截器,清理文件上傳時的臨時文件 fileParameterNames = multiWrapper.getFileParameterNames(); while (fileParameterNames != null && fileParameterNames.hasMoreElements()) { String inputValue = (String) fileParameterNames.nextElement(); File[] files = multiWrapper.getFiles(inputValue); for (File currentFile : files) { if (LOG.isInfoEnabled()) { LOG.info(getTextMessage(action, "struts.messages.removing.file", new Object[]{inputValue, currentFile}, ac.getLocale())); } if ((currentFile != null) && currentFile.isFile()) { if (currentFile.delete() == false) {//這裡調用了currentFile.delete()將臨時文件刪除,因為此時在Action對上傳的文件處理已經完成 LOG.warn("Resource Leaking: Could not remove uploaded file '"+currentFile.getCanonicalPath()+"'."); } } } } //返回上一個攔截器 return result; } 下面再去看一下該攔截器是如何判斷上傳的文件是否合法的,即acceptFile方法: [java] protected boolean acceptFile(Object action, File file, String filename, String contentType, String inputName, ValidationAware validation, Locale locale) { boolean fileIsAcceptable = false; // If it's null the upload failed if (file == null) { String errMsg = getTextMessage(action, "struts.messages.error.uploading", new Object[]{inputName}, locale); if (validation != null) { validation.addFieldError(inputName, errMsg); } LOG.warn(errMsg); } else if (maximumSize != null && maximumSize < file.length()) { String errMsg = getTextMessage(action, "struts.messages.error.file.too.large", new Object[]{inputName, filename, file.getName(), "" + file.length()}, locale); if (validation != null) { validation.addFieldError(inputName, errMsg); } LOG.warn(errMsg); } else if ((!allowedTypesSet.isEmpty()) && (!containsItem(allowedTypesSet, contentType))) { String errMsg = getTextMessage(action, "struts.messages.error.content.type.not.allowed", new Object[]{inputName, filename, file.getName(), contentType}, locale); if (validation != null) { validation.addFieldError(inputName, errMsg); } LOG.warn(errMsg); } else if ((! allowedExtensionsSet.isEmpty()) && (!hasAllowedExtension(allowedExtensionsSet, filename))) { String errMsg = getTextMessage(action, "struts.messages.error.file.extension.not.allowed", new Object[]{inputName, filename, file.getName(), contentType}, locale); if (validation != null) { validation.addFieldError(inputName, errMsg); } LOG.warn(errMsg); } else { fileIsAcceptable = true; } return fileIsAcceptable; } 該方法邏輯很簡單,就是依次判斷上傳的文件是否為null,是否符合設定的文件大小,是否符合設定的文件類型,是否符合設定的文件擴展名。如果沒有設定文件類型與文件擴展名則表示文件類型與文件擴展名沒有限制,而在defaultStack中,該攔截器的這兩個都是沒有進行設定的,但是上傳文件的最大大小是設置了的,肯定有人會說maximumSize不是也沒有設置嗎?沒錯,在該攔截器maximumSize的確沒有設置,默認值就是為null,剛剛也說了,真正處理文件上傳的request對象是JakartaMultiPartRequest,在JakartaMultiPartRequest中有一個相應的maxSize屬性在實例化JakartaMultiPartRequest對象的時候通過struts2的容器把該值注入進來了,該值的配置在struts2-core.jar中的org.apache.struts2包中的default.properties文件中(struts.multipart.maxSize=2097152),默認值為2097152,即2M大小。 我們在進行文件上傳的時候是在Action中定義相應的屬性來接收上傳的文件,文件的類型與文件名,但在大家發現沒有在該攔截器並沒有調用當前Action設置當前上傳文件的方法,即沒有調用相應的setXxx()、setXxxContentType()、setXxxFileName()方法,xxx為input的name屬性值,只是將上傳的文件,文件類型,文件名稱存儲到ActionContext的parameters Map中,這個問題在ParametersInterceptor攔截器進行講解。 這個攔截器與前面幾個攔截器有一點不一樣就是在執行了一定的邏輯後調用了下一個攔截器、Action,在執行完Action攔截器依次返回再返回到該攔截器繼續執行清理上傳文件時產生的臨時文件,這是由該攔截器要實現的功能所決定的。