为何使用XSSFWorkbook会修改原始Excel模板文件?
问题
在Jira Scriptrunner中用Groovy开发,借助Apache POI的XSSFWorkbook处理.xlsx模板文件。脚本仅在内存中修改工作簿,调用workbook.write(outputStream)和workbook.close()后,并未主动以原文件名保存到服务器,但服务器上的原始模板文件却被意外修改。
原代码如下:
import org.apache.poi.xssf.usermodel.XSSFWorkbook import releasemanagement.changes.AttachmentCreator import other.TMPConfig import com.atlassian.jira.issue.fields.CustomField import com.atlassian.jira.issue.CustomFieldManager import com.atlassian.jira.component.ComponentAccessor import org.apache.poi.xssf.usermodel.XSSFCell CustomFieldManager custFldManag = ComponentAccessor.getCustomFieldManager() def issue = Issues.getByKey("ISSUEKEY") def attachmentCreator = new AttachmentCreator() def attachmentManager = ComponentAccessor.getAttachmentManager() def wb = new XSSFWorkbook(new File("path/Template.xlsx")) CustomField currCF XSSFCell currCell String reportType = "Initial" TMPConfig.fieldToCell.each{k, v -> List<Integer> cellCoordinates = v.get(reportType) if(cellCoordinates){ currCell = wb.getSheetAt(cellCoordinates[0]).getRow(cellCoordinates[1]).getCell(cellCoordinates[2]) if(currCell){ currCF = custFldManag.getCustomFieldObject(k) currCell.setCellValue(currCF.getValue(issue).toString()) } } } ByteArrayOutputStream out = new ByteArrayOutputStream() wb.write(out) wb.close() ByteArrayInputStream inputStream = new ByteArrayInputStream(out.toByteArray()) out.close() int filename_num = 1 String filename = "Dora_${reportType}_${filename_num}.xlsx" attachmentCreator.createAttachmentForIssue(issue, filename, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", inputStream)
同事的临时解决代码:
XSSFWorkbook wb_temp = new XSSFWorkbook(new File("path/Template.xlsx")) ByteArrayOutputStream out_temp = new ByteArrayOutputStream() wb_temp.write(out_temp) wb_temp.close() ByteArrayInputStream inputStream_temp = new ByteArrayInputStream(out_temp.toByteArray()) out_temp.close() XSSFWorkbook wb = new XSSFWorkbook(inputStream_temp)
原因分析
这是Apache POI中XSSFWorkbook的默认行为导致的:
- 当直接用
File对象构造XSSFWorkbook时,POI为优化性能,会采用随机访问文件模式(底层依赖RandomAccessFile),而非一次性将整个文件加载到内存。 - 这种模式下,工作簿修改过程中,部分中间数据可能被写回原始
File对应的磁盘文件,尤其是调用close()方法时,POI会执行清理或同步操作,最终导致原始模板文件被篡改。 - 你以为只是在内存中修改,但POI的底层实现让工作簿始终和原始文件句柄保持关联,修改意外渗透到了磁盘文件。
临时方案的原理
同事的代码通过内存中转切断了工作簿与原始文件的关联:
- 先将原始模板加载到临时工作簿
wb_temp,再把它写入ByteArrayOutputStream(完全在内存中完成)。 - 关闭临时工作簿,释放与原始文件的关联。
- 从内存字节流构造新的
wb工作簿,此时该工作簿完全基于内存数据,和磁盘上的原始模板文件无任何关联。 - 后续修改这个内存工作簿,自然不会影响服务器上的原始模板文件。
内容的提问来源于stack exchange,提问作者TryingToPassCompSci
相关产品推荐
相关产品推荐

