聶永的博客

          記錄工作/學(xué)習(xí)的點點滴滴。

          Servlet 3.0筆記之超方便的文件上傳支持

          在以前,處理文件上傳是一個很痛苦的事情,大都借助于開源的上傳組件,諸如commons fileupload等。現(xiàn)在好了,很方便,便捷到比那些組件都方便至極。以前的HTML端上傳表單不用改變什么,還是一樣的multipart/form-data MIME類型。
          讓Servlet支持上傳,需要做兩件事情
          1. 需要添加MultipartConfig注解
          2. 從request對象中獲取Part文件對象
          但在具體實踐中,還是有一些細(xì)節(jié)處理,諸如設(shè)置上傳文件的最大值,上傳文件的保存路徑。
          需要熟悉MultipartConfig注解,標(biāo)注在@WebServlet之上,具有以下屬性:
          屬性名類型是否可選描述
          fileSizeThresholdint當(dāng)數(shù)據(jù)量大于該值時,內(nèi)容將被寫入文件。
          locationString存放生成的文件地址。
          maxFileSizelong允許上傳的文件最大值。默認(rèn)值為 -1,表示沒有限制。
          maxRequestSizelong針對該 multipart/form-data 請求的最大數(shù)量,默認(rèn)值為 -1,表示沒有限制。

          一些實踐建議:
          1. 若是上傳一個文件,僅僅需要設(shè)置maxFileSize熟悉即可。
          2. 上傳多個文件,可能需要設(shè)置maxRequestSize屬性,設(shè)定一次上傳數(shù)據(jù)的最大量。
          3. 上傳過程中無論是單個文件超過maxFileSize值,或者上傳總的數(shù)據(jù)量大于maxRequestSize值都會拋出IllegalStateException異常;
          4. location屬性,既是保存路徑(在寫入的時候,可以忽略路徑設(shè)定),又是上傳過程中臨時文件的保存路徑,一旦執(zhí)行Part.write方法之后,臨時文件將被自動清除。
          5. 但Servlet 3.0規(guī)范同時也說明,不提供獲取上傳文件名的方法,盡管我們可以通過part.getHeader("content-disposition")方法間接獲取得到。
          6. 如何讀取MultipartConfig注解屬性值,API沒有提供直接讀取的方法,只能手動獲取。
            來一個示例吧,上傳前臺頁面:
            后臺處理Servlet:
            /**
            * 上傳文件測試 location為臨時文件保存路徑
            *
            * @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 {
            // 為避免獲取文件名稱時出現(xiàn)亂碼
            request.setCharacterEncoding("UTF-8");

            Part part = null;
            try {
            // <input name="file" size="50" type="file" />
            part = request.getPart("file");
            } catch (IllegalStateException ise) {
            // 上傳文件超過注解所標(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, "上傳文件過大,請檢查輸入是否有誤!");
            return;
            } catch (IOException ieo) {
            // 在接收數(shù)據(jù)時出現(xià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)異常,請檢查輸入是否有誤!");
            return;
            }

            // 得到文件的原始名稱,eg :測試文檔.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);

            // 因在注解中指定了路徑,這里可以指定要寫入的文件名
            // 在未執(zhí)行write方法之前,將會在注解指定location路徑下生成一臨時文件
            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ù),很簡單:
             /**
            * 如何得到上傳的文件名, API沒有提供直接的方法,只能從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屬性,可以很容易從值中抽取出文件名。臨時生成的上傳文件大都以 .tmp為后綴,大致如下:
            讓上傳出現(xiàn)錯誤,就可以在保存路徑下看到大致如上的臨時文件。
            一次上傳多個文件的后臺servlet示范:
            /**
            * 多文件上傳支持
            * @author yongboy
            * @date 2011-1-14
            * @version 1.0
            */
            @MultipartConfig(
            location = "/home/yongboy/tmp/",
            maxFileSize = 1024L * 1024L, // 每一個文件的最大值
            maxRequestSize = 1024L * 1024L * 10L // 一次上傳最大值,若每次只能上傳一個文件,則設(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");

            Collection parts = null;
            try {
            parts = request.getParts();
            } catch (IllegalStateException ise) {
            // 可能某個文件大于指定文件容量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) {
            // 在獲取某個文件時遇到拉IO異常錯誤
            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;
            }

            // 前端具有幾個file組件,這里會對應(yīng)幾個Part對象
            List fileNames = 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);
            }
            }
            批量上傳很簡單,但也有風(fēng)險,任一個文件若超過maxFileSize值,意味著整個上傳都會失敗。若不限大小,那就不存在以上憂慮了。
            總之,在Servlet 3.0 中,無論是上傳一個文件,或者多個批量上傳都是非常簡單,但要處理好異常,避免出錯。

            posted on 2011-01-15 20:43 nieyong 閱讀(14736) 評論(2)  編輯  收藏 所屬分類: Servlet3

            評論

            # re: Servlet 3.0筆記之超方便的文件上傳支持 2011-03-18 14:22 - -

            謝謝  回復(fù)  更多評論   

            # re: Servlet 3.0筆記之超方便的文件上傳支持 2016-08-16 10:22 yxscomeon

            @- -
            謝謝,解決了我的一個問題!特意注冊過來謝謝的!  回復(fù)  更多評論   

            公告

            所有文章皆為原創(chuàng),若轉(zhuǎn)載請標(biāo)明出處,謝謝~

            新浪微博,歡迎關(guān)注:

            導(dǎo)航

            <2011年1月>
            2627282930311
            2345678
            9101112131415
            16171819202122
            23242526272829
            303112345

            統(tǒng)計

            常用鏈接

            留言簿(58)

            隨筆分類(130)

            隨筆檔案(151)

            個人收藏

            最新隨筆

            搜索

            最新評論

            閱讀排行榜

            評論排行榜

            主站蜘蛛池模板: 昌平区| 余江县| 武乡县| 大洼县| 邢台县| 陆川县| 仁布县| 南京市| 门源| 馆陶县| 福泉市| 江孜县| 汾西县| 肥西县| 临清市| 合肥市| 阿拉善盟| 屏南县| 通化县| 彭水| 涪陵区| 太康县| 灌云县| 剑川县| 白城市| 湄潭县| 新昌县| 昭觉县| 秦皇岛市| 梅河口市| 江孜县| 香河县| 晋江市| 昂仁县| 延吉市| 古田县| 珠海市| 紫阳县| 黄骅市| 农安县| 榆社县|