Servlet 3.0筆記之超方便的文件上傳支持
在以前,處理文件上傳是一個(gè)很痛苦的事情,大都借助于開(kāi)源的上傳組件,諸如commons fileupload等。現(xiàn)在好了,很方便,便捷到比那些組件都方便至極。以前的HTML端上傳表單不用改變什么,還是一樣的multipart/form-data MIME類型。
讓Servlet支持上傳,需要做兩件事情
- 需要添加MultipartConfig注解
- 從request對(duì)象中獲取Part文件對(duì)象
但在具體實(shí)踐中,還是有一些細(xì)節(jié)處理,諸如設(shè)置上傳文件的最大值,上傳文件的保存路徑。
需要熟悉MultipartConfig注解,標(biāo)注在@WebServlet之上,具有以下屬性:
屬性名 | 類型 | 是否可選 | 描述 |
---|---|---|---|
fileSizeThreshold | int | 是 | 當(dāng)數(shù)據(jù)量大于該值時(shí),內(nèi)容將被寫(xiě)入文件。 |
location | String | 是 | 存放生成的文件地址。 |
maxFileSize | long | 是 | 允許上傳的文件最大值。默認(rèn)值為 -1,表示沒(méi)有限制。 |
maxRequestSize | long | 是 | 針對(duì)該 multipart/form-data 請(qǐng)求的最大數(shù)量,默認(rèn)值為 -1,表示沒(méi)有限制。 |
一些實(shí)踐建議:
- 若是上傳一個(gè)文件,僅僅需要設(shè)置maxFileSize熟悉即可。
- 上傳多個(gè)文件,可能需要設(shè)置maxRequestSize屬性,設(shè)定一次上傳數(shù)據(jù)的最大量。
- 上傳過(guò)程中無(wú)論是單個(gè)文件超過(guò)maxFileSize值,或者上傳總的數(shù)據(jù)量大于maxRequestSize值都會(huì)拋出IllegalStateException異常;
- location屬性,既是保存路徑(在寫(xiě)入的時(shí)候,可以忽略路徑設(shè)定),又是上傳過(guò)程中臨時(shí)文件的保存路徑,一旦執(zhí)行Part.write方法之后,臨時(shí)文件將被自動(dòng)清除。
- 但Servlet 3.0規(guī)范同時(shí)也說(shuō)明,不提供獲取上傳文件名的方法,盡管我們可以通過(guò)part.getHeader("content-disposition")方法間接獲取得到。
- 如何讀取MultipartConfig注解屬性值,API沒(méi)有提供直接讀取的方法,只能手動(dòng)獲取。
來(lái)一個(gè)示例吧,上傳前臺(tái)頁(yè)面:

后臺(tái)處理Servlet:
/**
* 上傳文件測(cè)試 location為臨時(shí)文件保存路徑
*
* @author yongboy
* @date 2011-1-13
* @version 1.0
*/
@MultipartConfig(location = "/home/yongboy/tmp/", maxFileSize = 1024 * 1024 * 10)
@WebServlet("/upload")
public class UploadFileAction extends HttpServlet {
private static final long serialVersionUID = 92166165626L;
private static final Log log = LogFactory.getLog(UploadFileAction.class);
// 得到注解信息
private static final MultipartConfig config;
static {
config = UploadFileAction.class.getAnnotation(MultipartConfig.class);
}
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/upload.jsp").forward(request, response);
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// 為避免獲取文件名稱時(shí)出現(xiàn)亂碼
request.setCharacterEncoding("UTF-8");
Part part = null;
try {
// <input name="file" size="50" type="file" />
part = request.getPart("file");
} catch (IllegalStateException ise) {
// 上傳文件超過(guò)注解所標(biāo)注的maxRequestSize或maxFileSize值
if (config.maxRequestSize() == -1L) {
log.info("the Part in the request is larger than maxFileSize");
} else if (config.maxFileSize() == -1L) {
log.info("the request body is larger than maxRequestSize");
} else {
log.info("the request body is larger than maxRequestSize, or any Part in the request is larger than maxFileSize");
}
forwardErrorPage(request, response, "上傳文件過(guò)大,請(qǐng)檢查輸入是否有誤!");
return;
} catch (IOException ieo) {
// 在接收數(shù)據(jù)時(shí)出現(xiàn)問(wèn)題
log.error("I/O error occurred during the retrieval of the requested Part");
} catch (Exception e) {
log.error(e.toString());
e.printStackTrace();
}
if (part == null) {
forwardErrorPage(request, response, "上傳文件出現(xiàn)異常,請(qǐng)檢查輸入是否有誤!");
return;
}
// 得到文件的原始名稱,eg :測(cè)試文檔.pdf
String fileName = UploadUtils.getFileName(part);
log.info("contentType : " + part.getContentType());
log.info("fileName : " + fileName);
log.info("fileSize : " + part.getSize());
log.info("header names : ");
for (String headerName : part.getHeaderNames()) {
log.info(headerName + " : " + part.getHeader(headerName));
}
String saveName = System.currentTimeMillis() + "."
+ FilenameUtils.getExtension(fileName);
log.info("save the file with new name : " + saveName);
// 因在注解中指定了路徑,這里可以指定要寫(xiě)入的文件名
// 在未執(zhí)行write方法之前,將會(huì)在注解指定location路徑下生成一臨時(shí)文件
part.write(saveName);
request.setAttribute("fileName", fileName);
request.getRequestDispatcher("/uploadResult.jsp").forward(request,
response);
}
private void forwardErrorPage(HttpServletRequest request,
HttpServletResponse response, String errMsg)
throws ServletException, IOException {
request.setAttribute("errMsg", errMsg);
request.getRequestDispatcher("/upload.jsp").forward(request, response);
}
}
獲取文件名的函數(shù),很簡(jiǎn)單:
/**
* 如何得到上傳的文件名, API沒(méi)有提供直接的方法,只能從content-disposition屬性中獲取
*
* @param part
* @return
*/
protected static String getFileName(Part part) {
if (part == null)
return null;
String fileName = part.getHeader("content-disposition");
if (StringUtils.isBlank(fileName)) {
return null;
}
return StringUtils.substringBetween(fileName, "filename=\"", "\"");
}
文件上傳成功之后,以及日志輸出的截圖如下:

截圖中可以看到Part包含content-disposition屬性,可以很容易從值中抽取出文件名。臨時(shí)生成的上傳文件大都以 .tmp為后綴,大致如下:

讓上傳出現(xiàn)錯(cuò)誤,就可以在保存路徑下看到大致如上的臨時(shí)文件。
一次上傳多個(gè)文件的后臺(tái)servlet示范:
/**
* 多文件上傳支持
* @author yongboy
* @date 2011-1-14
* @version 1.0
*/
@MultipartConfig(
location = "/home/yongboy/tmp/",
maxFileSize = 1024L * 1024L, // 每一個(gè)文件的最大值
maxRequestSize = 1024L * 1024L * 10L // 一次上傳最大值,若每次只能上傳一個(gè)文件,則設(shè)置maxRequestSize意義不大
)
@WebServlet("/uploadFiles")
public class UploadFilesAction extends HttpServlet {
private static final long serialVersionUID = 2304820820384L;
private static final Log log = LogFactory.getLog(UploadFilesAction.class);
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/uploads.jsp").forward(request, response);
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
Collectionparts = null;
try {
parts = request.getParts();
} catch (IllegalStateException ise) {
// 可能某個(gè)文件大于指定文件容量maxFileSize,或者提交數(shù)據(jù)大于maxRequestSize
log.info("maybe the request body is larger than maxRequestSize, or any Part in the request is larger than maxFileSize");
} catch (IOException ioe) {
// 在獲取某個(gè)文件時(shí)遇到拉IO異常錯(cuò)誤
log.error("an I/O error occurred during the retrieval of the Part components of this request");
} catch (Exception e) {
log.error("the request body is larger than maxRequestSize, or any Part in the request is larger than maxFileSize");
e.printStackTrace();
}
if (parts == null || parts.isEmpty()) {
doError(request, response, "上傳文件為空!");
return;
}
// 前端具有幾個(gè)file組件,這里會(huì)對(duì)應(yīng)幾個(gè)Part對(duì)象
ListfileNames = new ArrayList ();
for (Part part : parts) {
if (part == null) {
continue;
}
// 這里直接以源文件名保存
String fileName = UploadUtils.getFileName(part);
if (StringUtils.isBlank(fileName)) {
continue;
}
part.write(fileName);
fileNames.add(fileName);
}
request.setAttribute("fileNames", fileNames);
request.getRequestDispatcher("/uploadsResult.jsp").forward(request,
response);
}
private void doError(HttpServletRequest request,
HttpServletResponse response, String errMsg)
throws ServletException, IOException {
request.setAttribute("errMsg", errMsg);
this.doGet(request, response);
}
}
批量上傳很簡(jiǎn)單,但也有風(fēng)險(xiǎn),任一個(gè)文件若超過(guò)maxFileSize值,意味著整個(gè)上傳都會(huì)失敗。若不限大小,那就不存在以上憂慮了。
總之,在Servlet 3.0 中,無(wú)論是上傳一個(gè)文件,或者多個(gè)批量上傳都是非常簡(jiǎn)單,但要處理好異常,避免出錯(cuò)。
posted on 2011-01-15 20:43 nieyong 閱讀(14720) 評(píng)論(2) 編輯 收藏 所屬分類: Servlet3