Java Servlet应用中如何使用Apache PDFBox处理Multipart文件?
问题描述
我选用Apache PDFBox是因为它处理大体积PDF时不会出现内存过载,比如我有个2GB、近1000页的PDF文件,需要逐页处理、上传并存入服务器(我无法直接访问该服务器)。目前我的应用仅支持通过本地文件路径(如C:\file.pdf)加载文件,无法适配Multipart文件上传场景。硬编码路径运行时程序正常,但始终无法将Multipart上传与PDFBox结合使用。以下是我的后端Servlet代码和前端JavaScript代码,求技术帮助。
后端Servlet代码
private void doWork(HttpServletRequest req, HttpServletResponse resp) throws Exception { if (!ServletFileUpload.isMultipartContent(req)) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Not multipart/form-data"); return; } ServletFileUpload upload = createFileUpload(); List<FileItem> fileItems = null; File file = parseRequest(req,upload); PDDocument document = Loader.loadPDF(file, MemoryUsageSetting.setupTempFileOnly()); PDFRenderer renderer = new PDFRenderer(document); try { for( PDPage page : document.getPages() ){ PdfDocumentHandler pdfDoc= new PdfDocumentHandler(); fileItems.add((FileItem) page); pageCount = pdfDoc.savePdf(fileItems); } } catch (Exception e) { log.error("Error: " + e.getMessage()); e.printStackTrace(); } } protected File parseRequest(HttpServletRequest req, ServletFileUpload upload)throws IOException, FileUploadException { return (File) upload.parseRequest(req); } private static ServletFileUpload createFileUpload() { ServletFileUpload upload = new ServletFileUpload( new DiskFileItemFactory(IN_MEMORY_FILE_SIZE_THRESHOLD, new File(TMP_DIR))); upload.setSizeMax(MAX_UPLOAD_FILE_SIZE); return upload; }
前端代码
HTML部分
<form action="../../index/AddPdf?parameters=<%=parameters%>&select=<%=idNode%>&save=<%=save%>&sesionInx=<%=sesionInx%>&esPDF=true&scanner=true" name="form1" id="form1" enctype="multipart/form-data" method="post"> <div id="divFileUpload" style="overflow: auto; width: 100%; height: 326; border-right: #333399 1px solid; border-top: #333399 1px solid; border-left: #333399 1px solid; border-bottom: #333399 1px solid"> <table id="tableFile" width="100%" align="center" cellspacing="0" cellpadding="0" border="0"> <tr> <td><input type="file" name="file0" id="file0" size="40" maxlength="256"></td> </tr> </table> </div> <input type="text" id="totFile" value="" size="3" readonly> <input type="button" value="Send" onclick="valExtension('tableFile')"> </form>
JavaScript部分
function valExtension(tableID) { var t = ""; var table = document.getElementById(tableID); for (var i = 0; i < table.rows.length; i++) { var o = document.getElementById("file" + i); var f = o.value.toLowerCase(); var t = f.substring(f.lastIndexOf(".") + 1); if (isWhitespace(t)) { alert("Select your file to upload"); o.focus(); return false; } else if (isNotValidExtension(t)) { alert("The file\"" + t + "\" is not a PDF"); o.focus(); return false; } } document.getElementById("form1").submit(); } function isNotValidExtension(e) { var types = ["PDF", "pdf"]; for (var j = 0; j < types.length; j++) if (e == types[j]) return false; return true; } function isWhitespace(s) { var whitespace = " \t\n\r"; if ((s == null) || (s.length == 0)) return true; for (var i = 0; i < s.length; i++) { var c = s.charAt(i); if (whitespace.indexOf(c) == -1) return false; } return true; }
解决方案
你的代码存在几个核心问题,直接导致Multipart上传与PDFBox无法正常结合,以下是修复步骤:
1. 修复parseRequest方法的类型错误
upload.parseRequest(req)返回的是List<FileItem>,不是File,强制类型转换会直接抛出异常。正确做法是从返回的列表中提取上传的文件项:
protected FileItem parseRequest(HttpServletRequest req, ServletFileUpload upload) throws IOException, FileUploadException { List<FileItem> items = upload.parseRequest(req); // 遍历找到非表单字段的文件项(对应前端name="file0"的上传控件) for (FileItem item : items) { if (!item.isFormField()) { return item; } } throw new IOException("未检测到上传的文件"); }
2. 移除错误的PDPage转FileItem逻辑
PDPage是PDFBox的页面对象,和FileItem没有继承关系,强制转换会抛出ClassCastException。如果需要逐页上传,需将单页PDF生成临时文件或字节流,再传给savePdf方法:
private void doWork(HttpServletRequest req, HttpServletResponse resp) throws Exception { if (!ServletFileUpload.isMultipartContent(req)) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Not multipart/form-data"); return; } ServletFileUpload upload = createFileUpload(); FileItem uploadedFileItem = parseRequest(req, upload); // 直接从上传文件的输入流加载PDF,避免额外磁盘IO try (InputStream inputStream = uploadedFileItem.getInputStream(); PDDocument document = Loader.loadPDF(inputStream, MemoryUsageSetting.setupTempFileOnly())) { int pageCount = 0; PdfDocumentHandler pdfDoc = new PdfDocumentHandler(); for (PDPage page : document.getPages()) { // 创建单页PDF文档 try (PDDocument singlePageDoc = new PDDocument()) { singlePageDoc.addPage(page); // 生成临时文件存储单页PDF File tempPageFile = File.createTempFile("single-page-", ".pdf"); singlePageDoc.save(tempPageFile); // 根据savePdf的参数要求传递文件,若必须用FileItem则自行封装 pageCount += pdfDoc.savePdf(List.of(createTempFileItem(tempPageFile))); // 标记临时文件在JVM退出时删除 tempPageFile.deleteOnExit(); } } resp.getWriter().write("成功处理 " + pageCount + " 页"); } catch (Exception e) { log.error("PDF处理失败: ", e); resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "PDF处理失败"); } finally { // 删除上传的临时文件 uploadedFileItem.delete(); } } // 若savePdf必须接收FileItem,可构造临时FileItem对象 private FileItem createTempFileItem(File file) throws IOException { DiskFileItemFactory factory = new DiskFileItemFactory(); return factory.createItem("singlePageFile", "application/pdf", false, file.getName()); }
3. 修复fileItems空指针问题
原代码中List<FileItem> fileItems = null;后直接调用add方法会抛出NullPointerException,需先初始化:List<FileItem> fileItems = new ArrayList<>();,不过修复上述逻辑后该变量大概率不再需要。
4. 前端验证逻辑优化
原验证依赖文件名后缀,可通过文件type属性和后缀双重验证,同时避免浏览器兼容性问题:
function valExtension(tableID) { var table = document.getElementById(tableID); for (var i = 0; i < table.rows.length; i++) { var o = document.getElementById("file" + i); if (!o.files || o.files.length === 0) { alert("请选择要上传的文件"); o.focus(); return false; } var file = o.files[0]; // 双重验证确保是PDF文件 if (file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')) { alert("文件\"" + file.name + "\"不是PDF格式"); o.focus(); return false; } } document.getElementById("form1").submit(); }
内容的提问来源于stack exchange,提问作者Michael Evan
相关产品推荐
相关产品推荐

