今天碰到需求,要对埋点数据做统计,想利用Excel提供的强大函数库,所以激灵把数据导出成Excel文档。又苦于Apache POI太过于繁琐,谷歌搜寻到这款基于模板的工具。 原文文档见此:JXLS Getting Started Guide
以下属于搬砖翻译,不喜请轻喷。
假设我们有一个employee对象的集合想导出为Excel。
我们的Employee类可能长这样
public class Employee {
private String name;
private Date birthDate;
private BigDecimal payment;
private BigDecimal bonus;
// ... constructors
// ... getters/setters
}
为了利用jxls导出为Excel,我们需要作如下操作
如下,我们针对这三步做详细说明
如果是使用Maven作为项目的管理工具,那么最简单的方式就是在配置文件中如下指定项目的依赖
<dependency>
<groupId>org.jxls</groupId>
<artifactId>jxls</artifactId>
<version>2.4.6</version>
</dependency>
或者你可以从官方站点下载Jxsl的发布包,然后将其应用于自己的项目。
除了添加jxsl的核心依赖,我们还需添加一份Jxls transformer
engine实现的依赖,用于执行所有从Java对象数据转换成Excel的底层操作。
就像在Main Concepts里Transformers这一节所描述的一样,Jxls的核心模块不去依赖任何一款具体的Java-Excel工具,而仅仅是通过一套预先定义好的接口去操作Excel。目前Jxls提供两套这个预先定义的接口的实现,分别是基于知名的Apache POI 和 Java Excel API工具。
transformer
实现<dependency>
<groupId>org.jxls</groupId>
<artifactId>jxls-poi</artifactId>
<version>1.0.15</version>
</dependency>
transformer
实现<dependency>
<groupId>org.jxls</groupId>
<artifactId>jxls-jexcel</artifactId>
<version>1.0.7</version>
</dependency>
这里要求的模板文件其实是一份用特定标记描述Jxls应该如何输出数据的Excel文档。
Jxls提供了内建的标记处理器(markup processor),可以通过其解析模板文件以及提前操作指令。
根据要求亦可自定义一份标记处理器。通过自定义的标记书写一份Excel模板,并解析为合法的Jxls指令集(Jxls Commands structure)。
如下一起去了解Jxls内建的标记处理器如何完成工作。
默认情况下,Jxls支持通过Apache JEXL表达式从Excel模板文件内访问Java对象的属性和方法。在Jxls的提供的上下文中,透过某个指定的key必须要能访问到这个Java对象。如:为了能输出员工的姓名,我们可以将${employee.name}
写于Excel的单元格中。通常情况下,我们将jexl表达式置于${
和}
之间。我们还需确保在Jxls的上下文中能通过employee
key访问Employee对象。
表示属性的符号是可配置的,你或许想使用形如[[employee.name]]
的表达式来表示。更多关于JEXL的用法,请查看Expression Language
然后最终的模板文件可能如下图,或点击此处下载
如图,第4行 所示,我们使用的就是上文讲解过的JEXL表达式来访问employee
对象的属性。
A1单元格包含了一个jx:area(lastCell="D4")
的批注,其中形如 Author: 的黑体字可以不用理会,为系统自动添加,也可能为
Administrator:,它指定了模板的根区域为 A1:D4
。
然后A4单元格批注中是一个Jxsl Each-Command jx:each(items="employees" var="employee" lastCell="D4")
。这个Each-Command会遍历存于Jxls上下文中employees
这个键下的集合,并将每一个集合项单次存入上下文内中由var
关键词定义的employee
键下。lastCell
属性定义的是Each-Command的单个body区域,为A4:D4
,往后它会被克隆并使用上下文中新Employee对象来循环处理数据。
在这个例子中,我们假设使用XlsCommentAreaBuilder
这个类来构造模板文件中Jxls的处理区域,通过这个类我们可以将Jxls命令写在单元格的批注中。若非如此,你也可以将Jxls命令写在Java代码中。
如下展示怎么使用Jxls API处理我们的Excel 模板
...
logger.info("Running Object Collection demo");
List<Employee> employees = generateSampleEmployeeData();
try(InputStream is = ObjectCollectionDemo.class.getResourceAsStream("object_collection_template.xls")) {
try (OutputStream os = new FileOutputStream("target/object_collection_output.xls")) {
Context context = new Context();
context.putVar("employees", employees);
JxlsHelper.getInstance().processTemplate(is, os, context);
}
}
...
在这个代码示例中,我们将从classpath读取模板文件object_collection_template.xls
。然后将处理结果输出于target/object_collection_output.xls
。
所有主要操作仅需一行代码
JxlsHelper.getInstance().processTemplate(is, os, context);
除非另做声明,JxlsHelper
会默认将数据覆盖在Excel模板内写有标记的sheet中。
或者像下面这样,选择将数据生成于另一个sheet
JxlsHelper.getInstance().processTemplateAtCell(is, os, context, "Result!A1");
这样一来,操作区域就修改成了sheetResult
的A1
单元格。
最终的结果如下图,或从此处下载
错误栈信息如下
...
2018-08-16 14:22:31.055 ERROR 43068 --- [nio-8080-exec-1] org.jxls.util.TransformerFactory : Method createTransformer of org.jxls.transform.poi.PoiTransformer class thrown an Exception
java.lang.reflect.InvocationTargetException: null
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_144]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_144]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_144]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_144]
at org.jxls.util.TransformerFactory.createTransformer(TransformerFactory.java:35) ~[jxls-2.4.6.jar:na]
at org.jxls.util.JxlsHelper.createTransformer(JxlsHelper.java:381) [jxls-2.4.6.jar:na]
at org.jxls.util.JxlsHelper.processTemplateAtCell(JxlsHelper.java:253) [jxls-2.4.6.jar:na]
...
...
2018-08-16 14:22:31.075 ERROR 43068 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: Cannot load XLS transformer. Please make sure a Transformer implementation is in classpath] with root cause
java.lang.IllegalStateException: Cannot load XLS transformer. Please make sure a Transformer implementation is in classpath
at org.jxls.util.JxlsHelper.createTransformer(JxlsHelper.java:383) ~[jxls-2.4.6.jar:na]
at org.jxls.util.JxlsHelper.processTemplateAtCell(JxlsHelper.java:253) ~[jxls-2.4.6.jar:na]
...
以上错误栈信息实为ObjectCollectionDemo.class.getResourceAsStream("object_collection_template.xls")
未能成功获取到文件流。
由此我们应该去了解下相关API,如ObjectClass.class.getClassLoader().getResourceAsStream("name")
和 ObjectClass.class.getResourceAsStream("name")
,以及FileSystem,绝对路径,相对路径相关知识。
此处,假设项目结构如下
\- project
\- src
+- main
| +- java
| | \- com.jxls.app
| | +- object_1_template.xls
| | \- ObjectCollectionDemo.java
| +- resources
| +- templates
| | \- object_3_template.xls
| \- object_2_template.xls
+- test
\- pom.xml
主要处理逻辑写于ObjectCollectionDemo.java内
ObjectCollectionDemo.class.getResourceAsStream("object_1_template.xls")
可以访问object_1_template.xls,因为模板1和当前类同包,可以通过相对路径去访问,如果模板一位于com.jxls
包下,是当前包的父包,则需通过..\object_1_template.xls
去访问ObjectCollectionDemo.class.getResourceAsStream("\object_2_template.xls")
可以访问object_2_template.xls,因为项目编译后模板2位于classpath根路径,则\
表示根路径,下面类似,templates
位于根路径下ObjectCollectionDemo.class.getResourceAsStream("\templates\object_3_template.xls")
可以访问object_3_template.xls