You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>
    &nbsp;&nbsp; <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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 15:37:06