ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Java读取Excel实战:从POI原理到性能优化与避坑指南

Java读取Excel实战:从POI原理到性能优化与避坑指南 1. 项目概述为什么Java处理Excel是个“技术活”做Java开发这么多年处理Excel文件的需求几乎在每个项目里都会遇到。从早期的报表导出、数据导入到现在的复杂数据分析、模板填充Excel作为办公领域的“事实标准”其与Java后端的交互一直是开发中的高频场景。但就是这个看似简单的“读Excel”背后却藏着不少门道。新手可能会被网上各种教程、不同的库jxl, poi搞得晕头转向老手也可能在性能、内存和兼容性上踩过坑。这篇文章我就结合自己踩过的无数坑和项目实战经验把Java读取Excel这件事从原理到实操从选型到避坑给你彻底讲明白。无论你是刚入门的新手还是想优化现有代码的资深开发者都能在这里找到清晰的路径和可落地的方案。2. 核心工具选型POI与JXL的深度对比与抉择当你接到一个读取Excel的需求时第一个要做的决定就是用什么库目前Java生态中Apache POI和JXL是两大主流选择。但它们的定位、能力和适用场景截然不同选错了后续的开发效率和系统稳定性都会大打折扣。2.1 Apache POI企业级应用的“瑞士军刀”Apache POI是Apache软件基金会的顶级项目可以说是Java处理Microsoft Office格式文件的“事实标准”。它的功能极其强大不仅支持读写新旧版本的Excel.xls和.xlsx还支持Word、PowerPoint等格式。为什么POI是大多数场景的首选格式支持全面这是POI最核心的优势。它完美支持Office Open XML格式.xlsx, .docx等这是Office 2007及以后版本使用的基于XML的格式。同时它也通过HSSF组件支持旧的二进制格式.xls。这意味着无论用户上传的是老版本的Excel 97-2003文件还是新版本的文件你的程序都能处理。功能强大POI提供了对Excel几乎所有特性的底层访问能力包括单元格样式字体、颜色、边框、公式计算、图表、数据验证、合并单元格、批注等。如果你需要生成样式复杂的报表或者需要读取包含公式的单元格的值POI几乎是唯一的选择。活跃的社区和持续的更新作为Apache项目POI拥有庞大的用户群和活跃的开发者社区bug修复及时对新版Office特性的跟进也很快。这意味着项目的长期维护有保障。POI的核心模块poi-ooxml: 用于处理.xlsx等OOXML格式。这是目前最常用的依赖。poi: 用于处理旧的.xls格式。通常poi-ooxml会依赖它。poi-scratchpad: 包含一些额外的组件如处理Visio等。Maven依赖最常用组合dependency groupIdorg.apache.poi/groupId artifactIdpoi-ooxml/artifactId version5.2.3/version !-- 请使用最新稳定版 -- /dependency引入这一个就会自动引入poi等核心依赖。注意POI的版本迭代较快建议使用官方Maven仓库中的最新稳定版。不同大版本间如3.x到4.x4.x到5.xAPI可能有较大变动升级时需仔细测试。2.2 JXLJava Excel API轻量级读取的“遗老”JXL是一个更老、更轻量级的库它只支持读取旧的.xls二进制格式不支持.xlsx格式。在POI尚未成熟或项目资源极其受限的早期JXL是一个不错的选择。JXL的现状与适用场景仅支持.xls这是它的致命伤。在Office 2007发布十多年后的今天用户使用.xlsx文件已是常态。如果你的应用明确只需要处理非常古老的系统生成的.xls文件且未来也不会变化那么JXL的轻量级特性或许还有一丝价值。API简单JXL的API确实比早期POI的API要简洁一些学习曲线稍低。开发停滞JXL已经多年没有重大更新社区基本处于停滞状态。这意味着遇到新问题或兼容性问题很难找到支持。结论对于几乎所有新的Java项目我强烈建议直接使用Apache POI并彻底放弃JXL。选择JXL相当于给自己埋下了一个“格式兼容性”的地雷未来一旦需要处理.xlsx文件整个读写逻辑都需要重写代价巨大。POI虽然稍显庞大但其功能、兼容性和社区支持带来的长期收益远超过那一点点初始的学习成本和库大小。2.3 其他选型与考量除了POI市面上还有一些其他库如EasyExcel阿里开源。EasyExcel的核心优势在于解决POI在处理超大Excel文件几十万、上百万行时的内存溢出OOM问题。它采用SAX模式解析逐行读取内存中只保留一行数据因此非常节省内存。如果你的核心场景是导入海量数据那么EasyExcel是比原生POI更优的选择。但需要注意的是EasyExcel底层依然依赖POI可以看作是对POI在特定场景下的高性能封装。对于常规的读取、复杂样式处理POI的原生API更为直接和强大。选型速查表特性Apache POIJXLEasyExcel (基于POI)支持格式.xls, .xlsx, .docx, .pptx 等仅 .xls.xls, .xlsx核心优势功能全面、格式支持全、标准轻量已过时海量数据读取性能、内存友好内存模式用户模式全部加载、SXSSF流式写全部加载SAX模式流式读适用场景绝大多数读写场景特别是需要样式、公式等遗留系统维护仅.xls大数据量导入读社区活跃度非常活跃停滞活跃3. 基于Apache POI的Excel读取实战详解确定了使用POI我们来深入核心的读取操作。POI将Excel文件抽象为Workbook工作簿里面包含多个Sheet工作表每个Sheet由Row行和Cell单元格组成。理解这个对象模型是正确使用API的关键。3.1 基础读取从文件到单元格值我们从一个最简单的例子开始读取一个已知路径的Excel文件并打印出第一个工作表的所有内容。import org.apache.poi.ss.usermodel.*; import java.io.FileInputStream; public class BasicExcelReader { public static void main(String[] args) { String filePath path/to/your/file.xlsx; // 文件路径 try (FileInputStream fis new FileInputStream(filePath); Workbook workbook WorkbookFactory.create(fis)) { // 1. 创建Workbook // 2. 获取第一个工作表索引从0开始 Sheet sheet workbook.getSheetAt(0); // 3. 遍历每一行 for (Row row : sheet) { // 4. 遍历行中的每一个单元格 for (Cell cell : row) { // 5. 根据单元格类型获取值 CellType cellType cell.getCellType(); Object cellValue; switch (cellType) { case STRING: cellValue cell.getStringCellValue(); break; case NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { cellValue cell.getDateCellValue(); // 日期 } else { cellValue cell.getNumericCellValue(); // 数字 } break; case BOOLEAN: cellValue cell.getBooleanCellValue(); break; case FORMULA: cellValue cell.getCellFormula(); // 获取公式字符串如“SUM(A1:B1)” // 如果需要计算公式结果可以使用 // FormulaEvaluator evaluator workbook.getCreationHelper().createFormulaEvaluator(); // CellValue evaluatedValue evaluator.evaluate(cell); // cellValue evaluatedValue.getNumberValue(); // 或其他类型 break; case BLANK: cellValue ; break; default: cellValue 未知类型; } System.out.print(cellValue \t); } System.out.println(); // 换行 } } catch (Exception e) { e.printStackTrace(); } } }关键点解析WorkbookFactory.create(...)这是创建Workbook对象的推荐方式。它会根据文件头信息自动判断是.xls返回HSSFWorkbook还是.xlsx返回XSSFWorkbook无需开发者关心底层实现。使用try-with-resources语法确保流和Workbook被正确关闭释放资源。sheet.getRow(rowNum)与for (Row row : sheet)前者通过行号从0开始获取特定行可能返回null如果该行不存在。后者使用迭代器遍历所有物理上存在的行会跳过中间的空行。根据需求选择。单元格类型判断这是读取的核心。必须通过cell.getCellType()判断类型后再取值。如果对一个文本类型的单元格调用getNumericCellValue()会直接抛出IllegalStateException。日期是一种特殊的数字类型需要用DateUtil.isCellDateFormatted(cell)来鉴别。公式处理对于公式单元格CellType.FORMULAgetCellFormula()返回公式字符串本身。如果你需要获取公式计算后的结果必须使用FormulaEvaluator进行求值。注意POI本身不包含计算引擎它依赖于单元格中已缓存的计算结果如果Excel文件保存了的话或者进行基本计算。3.2 处理空行与空单元格的策略在实际数据文件中空行和空单元格的处理不当是导致数据错位的常见原因。// 假设我们知道数据最多有100行每行最多有10列 int totalRows 100; int totalCols 10; for (int i 0; i totalRows; i) { Row row sheet.getRow(i); // 按索引获取可能为null for (int j 0; j totalCols; j) { String value ; if (row ! null) { Cell cell row.getCell(j, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); // CREATE_NULL_AS_BLANK策略如果单元格不存在则视作一个空白单元格对象而不是null if (cell.getCellType() CellType.STRING) { value cell.getStringCellValue().trim(); } else if (cell.getCellType() CellType.NUMERIC) { value String.valueOf(cell.getNumericCellValue()); } // ... 处理其他类型 } System.out.print(value \t); } System.out.println(); }使用Row.MissingCellPolicy在通过索引获取单元格时这是一个非常重要的参数。它有三种策略RETURN_NULL_AND_BLANK默认策略。如果单元格不存在返回null如果是空白单元格返回一个CellType为BLANK的单元格对象。RETURN_BLANK_AS_NULL如果单元格不存在或为空白都返回null。CREATE_NULL_AS_BLANK最常用在数据读取场景。如果单元格不存在返回一个CellType为BLANK的单元格对象。这样可以避免NullPointerException统一将不存在的单元格当作空白处理简化后续逻辑。3.3 读取复杂数据类型日期、富文本与错误值日期读取的坑Excel内部将日期存储为数字自1900年1月0日或1904年1月0日以来的天数。直接getNumericCellValue()得到的是一个浮点数。必须通过DateUtil.isCellDateFormatted(cell)判断并用cell.getDateCellValue()获取java.util.Date对象。注意时区问题getDateCellValue()返回的是基于系统时区的Date。if (cell.getCellType() CellType.NUMERIC DateUtil.isCellDateFormatted(cell)) { Date javaDate cell.getDateCellValue(); // 转换为java.time (JDK 8) Instant instant javaDate.toInstant(); LocalDateTime localDateTime LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); System.out.println(localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE)); }富文本单元格一个单元格内可能包含不同格式的文本片段如部分加粗。使用cell.getRichStringCellValue()获取RichTextString对象再调用.getString()获取纯文本。如果需要格式信息可以遍历其Font对象。错误值单元格当单元格类型为CellType.ERROR时表示单元格包含错误如#DIV/0!,#N/A。可以通过cell.getErrorCellValue()获取一个byte类型的错误码。POI提供了FormulaError枚举来映射这些错误码。if (cell.getCellType() CellType.ERROR) { byte errorCode cell.getErrorCellValue(); String errorString FormulaError.forInt(errorCode).getString(); System.out.println(单元格错误: errorString); }4. 高级应用与性能优化实战当数据量变大或逻辑变复杂时基础读取方式可能遇到性能瓶颈或内存问题。下面介绍几种高级模式和优化技巧。4.1 事件模型EventModel读取应对海量数据的利器对于几百兆甚至上G的Excel文件使用传统的Workbook对象将整个文件加载到内存用户模式会导致OutOfMemoryError。POI提供了基于事件驱动的SAX解析器XSSF and SAX (Event API)它逐行解析文件内存中只保留当前行的数据。核心思想你向解析器注册一个事件处理器DefaultHandler当解析器遇到文档开始、行开始、单元格数据等事件时会回调你的处理器方法。你需要自己维护状态来构建业务对象。import org.apache.poi.xssf.eventusermodel.XSSFReader; import org.apache.poi.xssf.model.SharedStringsTable; import org.apache.poi.openxml4j.opc.OPCPackage; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory; public class HugeExcelReader { public void processLargeFile(String filePath) throws Exception { try (OPCPackage pkg OPCPackage.open(filePath)) { XSSFReader reader new XSSFReader(pkg); SharedStringsTable sst reader.getSharedStringsTable(); // 共享字符串表 XMLReader parser XMLReaderFactory.createXMLReader(); // 设置自定义的内容处理器 MySheetHandler handler new MySheetHandler(sst); parser.setContentHandler(handler); // 获取所有工作表的输入流并解析 IteratorInputStream sheets reader.getSheetsData(); while (sheets.hasNext()) { InputStream sheetStream sheets.next(); InputSource sheetSource new InputSource(sheetStream); parser.parse(sheetSource); // 触发事件回调 sheetStream.close(); } } } } // 你需要实现一个继承自DefaultHandler的MySheetHandler类 // 在startElement, characters, endElement等方法中捕获标签如row, c, v并解析数据实操心得事件模型非常高效但代价是代码复杂度急剧上升。你需要处理XML解析的细节自己跟踪行号、列号处理单元格类型和共享字符串。除非是处理真正的海量数据否则不建议直接使用。对于大数据量导入优先考虑阿里开源的EasyExcel它完美封装了POI的事件模型提供了简单易用的回调接口AnalysisEventListener极大降低了使用门槛。4.2 使用SXSSF进行流式读取读优化POI的SXSSF模块原本是为流式写入超大文件设计的。但从POI 5.2.0开始实验性地支持了SXSSFWorkbook的读取模式。它允许你以滑动窗口的方式访问行窗口外的行会被清除有助于控制内存。// 注意此为POI 5.2.0的实验性功能API可能变动 import org.apache.poi.xssf.streaming.SXSSFWorkbook; // ... 创建SXSSFWorkbook时指定一个“行访问窗口大小” try (SXSSFWorkbook workbook new SXSSFWorkbook(new XSSFWorkbook(fis), 100)) { // 窗口大小为100行 Sheet sheet workbook.getSheetAt(0); for (Row row : sheet) { // 处理行数据 // 当遍历超过100行后最早的行会被从内存中丢弃 } }注意事项该功能尚处于“实验性”阶段且主要优化场景是按顺序一次性读取。如果需要随机访问不同位置的行传统方式或事件模型更合适。4.3 缓存与资源管理避免内存泄漏POI对象占用内存较大不正确的使用会导致内存泄漏尤其是在Web服务器这种长期运行的环境中。始终使用try-with-resources确保FileInputStream和Workbook对象被关闭。Workbook实现了Closeable接口。// 正确做法 try (Workbook workbook WorkbookFactory.create(new File(test.xlsx))) { // 使用workbook } // 自动关闭释放资源警惕循环中创建大量Workbook在循环内反复读取大量小文件每次都会创建和销毁Workbook对象可能引发频繁GC。可以考虑使用对象池如Commons Pool来复用Workbook对象但要注意线程安全和状态清理。及时清理不需要的Sheet/Row引用在处理完一个非常大的Sheet后如果业务逻辑允许可以主动将引用置为null帮助GC回收内存。但更根本的解决方案是使用事件模型处理大文件。5. 常见问题排查与实战避坑指南这一部分是我多年踩坑经验的总结很多问题在官方文档中不会强调但在实际开发中却经常遇到。5.1 日期读取错误1900年2月29日的“幽灵日期”Excel为了兼容Lotus 1-2-3错误地将1900年视为闰年因此1900年2月29日这个不存在的日期在Excel里是合法的对应数字60。POI的DateUtil类在解析时会自动修正这个错误。当你读取数字60时getDateCellValue()返回的是1900-3-1。这一点需要知晓在涉及非常古老日期的精确计算时要留意。5.2 数字精度问题与BigDecimal转换Excel中数字以浮点数double形式存储。Java的double存在精度损失问题。对于金额、身份证号等需要精确计算的数值直接使用cell.getNumericCellValue()得到的double进行运算可能导致误差。解决方案使用BigDecimal进行转换并指定舍入模式。if (cell.getCellType() CellType.NUMERIC !DateUtil.isCellDateFormatted(cell)) { double numericValue cell.getNumericCellValue(); // 错误做法直接 new BigDecimal(numericValue)仍会传入不精确的double // 正确做法先转为String再构造BigDecimal BigDecimal preciseValue new BigDecimal(String.valueOf(numericValue)); // 或者使用BigDecimal.valueOf它内部调用Double.toString BigDecimal preciseValue2 BigDecimal.valueOf(numericValue); System.out.println(精确值: preciseValue); }5.3 公式求值获取计算结果而非公式字符串当你读取一个包含公式如A1B1的单元格时cell.getCellType()返回的是FORMULA。此时有两种需求获取公式本身cell.getCellFormula()。获取公式计算后的结果需要使用FormulaEvaluator。if (cell.getCellType() CellType.FORMULA) { FormulaEvaluator evaluator workbook.getCreationHelper().createFormulaEvaluator(); CellValue cellValue evaluator.evaluate(cell); // 对单元格进行求值 switch (cellValue.getCellType()) { case NUMERIC: System.out.println(公式结果(数字): cellValue.getNumberValue()); break; case STRING: System.out.println(公式结果(文本): cellValue.getStringValue()); break; case BOOLEAN: System.out.println(公式结果(布尔): cellValue.getBooleanValue()); break; case ERROR: System.out.println(公式计算错误: FormulaError.forInt(cellValue.getErrorValue()).getString()); break; default: System.out.println(未知结果类型); } }重要提示FormulaEvaluator的求值依赖于单元格中是否缓存了上次计算的结果。如果Excel文件在保存时选择了“不保存公式结果”或者文件是程序生成的且未经过Excel计算那么evaluator.evaluate(cell)可能无法得到正确结果甚至可能触发POI内部的公式计算部分支持但这并不完全可靠。对于生产环境如果依赖公式结果最好确保源文件是经过Excel计算并保存的。5.4 内存溢出OOM问题深度剖析与解决这是处理大Excel文件时最经典的问题。症状通常是程序运行一段时间后抛出java.lang.OutOfMemoryError: Java heap space。根因分析用户模式UserModel的内存占用XSSFWorkbook处理.xlsx将整个XML文档解析后以DOM树的形式保存在内存中。一个包含几十万行、几十列的.xlsx文件其内存占用可能轻松达到几百MB甚至上GB远超JVM默认堆大小。共享字符串表SharedStringsTable.xlsx文件为了压缩会将所有字符串集中存储在一个“共享字符串表”中单元格只存储索引。POI在读取时会将整个共享字符串表加载到内存。如果文件中有大量重复的字符串这能节省内存但如果字符串几乎都不重复这个表本身就会非常大。解决方案矩阵问题场景推荐方案关键措施文件不大10万行增加JVM堆内存启动参数-Xms512m -Xmx2048m文件很大只需顺序读取事件模型Event API或EasyExcel使用XSSFReader SAX解析或直接引入EasyExcel文件很大需随机访问少量行使用SXSSFWorkbook读取窗口实验性创建SXSSFWorkbook时指定合适的rowAccessWindowSize文件很大且需复杂处理样式、公式很难完美解决考虑将文件拆分成多个小文件处理或使用专业ETL工具一个真实的避坑案例我们曾有一个数据导入功能用户上传的模板只有100列但数据有50万行。使用传统方式读取内存瞬间涨到1.5G后OOM。后来我们分析发现很多列是空的但POI仍然为每个空单元格创建了对象。优化方案是在读取循环中使用row.getLastCellNum()获取该行最后一个有内容的列索引而不是遍历所有理论列数减少了对大量空单元格对象的创建和访问。for (Row row : sheet) { short lastCellNum row.getLastCellNum(); // 注意返回的是最后一列的索引1 for (int cellIdx 0; cellIdx lastCellNum; cellIdx) { Cell cell row.getCell(cellIdx, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); // 处理cell } }5.5 编码与字体导致的乱码问题这个问题在读取包含中文等非ASCII字符的.xls文件时较为常见。.xls文件内部可能使用系统默认编码如GBK存储字符串而Java默认使用UTF-8读取导致乱码。解决方案对于POI字符串编码通常由库内部处理一般不会出问题。但如果遇到乱码可以尝试在创建HSSFWorkbook时指定编码但API不直接支持。更常见的做法是确保生成源文件的程序使用正确的编码。一个变通方法是如果确定是GBK乱码可以在获取字符串后尝试转换String value cell.getStringCellValue(); // 如果怀疑是GBK乱码成UTF-8的样子 try { String correctedValue new String(value.getBytes(ISO-8859-1), GBK); System.out.println(correctedValue); } catch (UnsupportedEncodingException e) { // 处理异常 }但这属于事后补救最好的办法是从源头保证编码一致。5.6 依赖冲突与版本问题POI依赖一系列其他库如commons-codec,commons-collections4,xmlbeans等。在Spring Boot等大型项目中容易发生传递依赖版本冲突。典型错误ClassNotFoundException: org.apache.xmlbeans.XmlObject或NoSuchMethodError。排查与解决使用mvn dependency:tree命令查看完整的依赖树检查是否有多个不同版本的POI子模块或传递依赖。在pom.xml中显式声明POI相关依赖的版本并利用exclusions排除冲突的低版本依赖。一个常见的冲突点是xmlbeans。POI 5.x 需要xmlbeans5.x而一些老项目可能依赖了旧的3.x版本。dependency groupIdorg.apache.poi/groupId artifactIdpoi-ooxml/artifactId version5.2.3/version exclusions !-- 排除可能冲突的低版本xmlbeans -- exclusion groupIdorg.apache.xmlbeans/groupId artifactIdxmlbeans/artifactId /exclusion /exclusions /dependency !-- 显式引入正确版本的xmlbeans -- dependency groupIdorg.apache.xmlbeans/groupId artifactIdxmlbeans/artifactId version5.1.1/version /dependency6. 工程化实践构建健壮的Excel读取工具类在实际项目中我们很少将读取逻辑散落在业务代码中。封装一个健壮、可配置、易用的工具类是更佳实践。这个工具类需要兼顾功能、性能和易用性。6.1 设计一个支持泛型的读取器我们可以设计一个接口和通用实现利用Java泛型和反射将行数据自动映射到Java对象。public interface ExcelReaderT { /** * 从输入流读取Excel数据 * param inputStream Excel文件流 * param sheetIndex 工作表索引 * param startRow 起始行0-based * param targetClass 目标对象类型 * return 对象列表 */ ListT read(InputStream inputStream, int sheetIndex, int startRow, ClassT targetClass) throws Exception; /** * 使用事件模型读取海量数据 * param filePath 文件路径 * param eventListener 行事件监听器 */ void readLargeFile(String filePath, RowEventListenerT eventListener) throws Exception; } // 行事件监听器接口 public interface RowEventListenerT { void handleRow(int rowIndex, ListString rowData); void onSheetEnd(); }6.2 基于注解的映射配置通过自定义注解来定义Excel列与Java对象字段的映射关系避免硬编码。Target(ElementType.FIELD) Retention(RetentionPolicy.RUNTIME) public interface ExcelColumn { // 列索引从0开始 int index(); // 列标题可用于校验或映射 String header() default ; // 数据类型转换器 Class? extends CellValueConverter converter() default DefaultConverter.class; } // 使用示例 public class UserDTO { ExcelColumn(index 0, header 姓名) private String name; ExcelColumn(index 1, header 年龄) private Integer age; ExcelColumn(index 2, header 入职日期, converter DateConverter.class) private LocalDate joinDate; // getters and setters }6.3 集成校验框架如Hibernate Validator在数据映射完成后自动进行JSR-303 Bean Validation确保数据的有效性。public class ValidatingExcelReaderT implements ExcelReaderT { private Validator validator; public ValidatingExcelReader() { ValidatorFactory factory Validation.buildDefaultValidatorFactory(); this.validator factory.getValidator(); } Override public ListT read(InputStream inputStream, int sheetIndex, int startRow, ClassT targetClass) throws Exception { ListT result new ArrayList(); // ... 使用POI读取行数据 ... for (ListString rowData : rawData) { T obj convertRowToObject(rowData, targetClass); SetConstraintViolationT violations validator.validate(obj); if (violations.isEmpty()) { result.add(obj); } else { // 收集校验错误记录日志或抛出业务异常 log.error(数据校验失败: {}, violations); } } return result; } // ... convertRowToObject 方法实现 ... }6.4 添加监控与性能日志在生产环境中需要监控Excel读取任务的性能和数据质量。public class MonitoredExcelReaderT implements ExcelReaderT { private ExcelReaderT delegate; private MeterRegistry meterRegistry; // 假设使用Micrometer public MonitoredExcelReader(ExcelReaderT delegate, MeterRegistry meterRegistry) { this.delegate delegate; this.meterRegistry meterRegistry; } Override public ListT read(InputStream inputStream, int sheetIndex, int startRow, ClassT targetClass) throws Exception { Timer.Sample sample Timer.start(meterRegistry); try { ListT result delegate.read(inputStream, sheetIndex, startRow, targetClass); sample.stop(meterRegistry.timer(excel.read.time, class, targetClass.getSimpleName())); meterRegistry.counter(excel.read.rows, class, targetClass.getSimpleName()).increment(result.size()); return result; } catch (Exception e) { meterRegistry.counter(excel.read.errors, class, targetClass.getSimpleName()).increment(); throw e; } } }通过这样的分层设计你的Excel读取工具将具备高度的可维护性、可扩展性和可观测性能够从容应对从简单到复杂的各种业务场景。记住没有一劳永逸的银弹根据你的数据量、性能要求和业务复杂度灵活组合使用上述模式和技术才是工程师价值的体现。
返回列表