计算机与人相比,有什么优势?这个问题从计算机诞生之初就不断被回答:在重复性工作上,计算机有人类无法企及的计算速度和存储空间。因此,把重复性工作交给计算机,也就是常说的“可复用性”,是软件设计中的一个最基本的思想。

这里记录的这个小设计,就是把重复工作自动化处理的一个例子。

背景

在我们的系统中,经常有用户上传一个Excel文件、系统进行处理的需求。

Excel的处理框架,有Jxl和POI。我们的系统使用了POI框架,并封装了若干个Util工具类。但是,由于工具类的封装不到位,业务代码中仍然会有大量的解析Excel文件(并且还存留了很多只接受.xls文件、无法处理.xlsx文件的代码)、遍历sheet/row/cell、处理单元格和Java对象类型转换的重复代码。    

因此,我设计实现了这个小小的工具。

思路

Excel导入的基本流程如下图所示。这个导入工具封装了校验、Excel转pojo这两个步骤,数据处理则作为扩展点留给了业务代码。

流程图

除基本功能外,这个框架还需要考虑性能问题。因此,方案中要为Excel转pojo和数据处理这两个操作预留异步的扩展点,以期提高处理效率。并且据我以前的测试,一个大小为1MB的Excel文件,放入JVM中之后要占用约2MB的内存空间。所以这个框架还应尽量节省(或者尽快释放)内存空间。

类图

Excel导出框架-类图

文件导入框架的入口是FileImportor接口。主方法入参是一个标记接口,其中要求提供字节数组,作为待导入的文件数据。导入时如果还需要其它数据,可以在实现接口时自行扩展、提供。

这里将文件转化为字节数组,主要有两点考虑。一方面,这样做可以兼容不同格式、不同封装的文件。例如,无论是MultipartFile还是File、无论是txt还是doc文件,都可以在转成字节数组后纳入框架中来进行处理。另一方面,无论是File还是InputStream,都会占用句柄、连接等资源。管理这些资源并不是这个框架的职能——事实上,技术框架都无法确定业务资源应当何时关闭。因此,接口只接受字节数组形式的入参。

框架中实现了一个Excel的导入类FileImportor4Excel。这个类使用POI框架作为底层工具。其中处理其实很简单,就是使用POI解析出excel文件后,遍历其中的sheet/row/cell,将其中数据转换为Java封装类。

解析Excel的流程中,将数据转换为Java封装类是比较困难的一个点。因为Excel中的数据类型只有那么几种,定义为Cell中的几个int常量。但是Java类型却有千千万。如何把它们正确的转换为Java类型?框架中引入了CellValueTransfer和ExcelImportHelper这两个类。CellValueTransfer是一个接口,定义了将POI的Cell转换为Java类的方法,并提供了若干个基础实现类。而ExcelImportHelper则是一个辅助类,用于为当前的Cell和Java类找到合适的CellValueTransfer,并执行转换操作。

后来增加了FileImportor4ExcelParallel,引入了多线程来处理。其中使用的线程池是ForkJoinPool,因为Excel导入恰可以“分而治之”。不过引入多线程后,整个Excel中的数据就无法在同一个事务中进行处理了。

此外还增加了一个带回调函数的类FileImportor4ExcelCallback。因为原逻辑中,需要将Excel全部转换为List然后再做处理。加入Callback之后,则可以每转换一条数据就处理一条数据,并且可以在处理完成后迅速丢弃(设置为null)该数据,以尽快释放内存。

代码

参见GitHub-fileimport

不过这里只有早期的部分代码,不包括后来的优化整理和功能扩展。