JXLS 库导出复杂 Excel

JXLS 库导出复杂 Excel

  JXLS java 库导出复杂 excel。JXLS 简述、与 Apache POI 区别、使用姿势、核心特性(如 XLS 区域、单数据绑定、循环、条件判断、公式支持、多工作表、图像插入、合并单元格、单元格样式)、各功能使用示例等。


------ 2025 年 5 月 21 日 甲辰年四月二十四 小满

版本

  • jdk:17
  • spring boot:3.2.2
  • jxls:2.14.0
  • apache poi:5.2.2

目录

    • [JXLS 库导出复杂 Excel](#JXLS 库导出复杂 Excel)
      • [1 概述](#1 概述)
        • [1.1 什么是 JXLS](#1.1 什么是 JXLS)
        • [1.2 JXLS 与 Apache POI 区别](#1.2 JXLS 与 Apache POI 区别)
      • [2 使用姿势](#2 使用姿势)
        • [2.1 引入依赖](#2.1 引入依赖)
        • [2.2 设计模板](#2.2 设计模板)
        • [2.3 添加标记](#2.3 添加标记)
        • [2.4 准备数据](#2.4 准备数据)
        • [2.5 生成 excel](#2.5 生成 excel)
      • [3 核心特性](#3 核心特性)
        • [3.1 XLS 区域](#3.1 XLS 区域)
        • [3.2 单数据绑定](#3.2 单数据绑定)
        • [3.3 循环](#3.3 循环)
        • [3.4 条件判断](#3.4 条件判断)
        • [3.5 公式支持](#3.5 公式支持)
        • [3.6 多工作表](#3.6 多工作表)
        • [3.7 图像插入](#3.7 图像插入)
        • [3.8 合并单元格](#3.8 合并单元格)
        • [3.9 单元格样式](#3.9 单元格样式)
      • [4 课后小知识](#4 课后小知识)
        • [4.1 图像 svg 转 png](#4.1 图像 svg 转 png)

1 概述

1.1 什么是 JXLS

  JXLS 是一个构建在 Apache POI 之上、基于模板的、高层抽象的、开源的 java 库,专门用于简化 excel 的生成。其核心思想是将 excel 的布局设计与数据填充逻辑分离。JXLS 官方文档。

1.2 JXLS 与 Apache POI 区别
特性 Apache POI JXLS
核心思想 底层、全面的 excel API,直接操作 Microsoft office 基于模板,通过在模板的标注/批注中使用模板语法来进行数据渲染
优点 功能及其强大灵活,几乎可实现 excel 支持的所有操作 大幅简化 excel 生成,维护方便(修改模板即可)
缺点 代码冗长、繁琐,尤其是操作复杂 excel 时;学习曲线相对陡峭; 受限于模板引擎,灵活性略低;需要额外学习模板语法(但相对简单);
主要用途 精细操作、读取解析、高级特性、动态结构、其它 office 格式 快速生成格式化的数据报表

2 使用姿势

2.1 引入依赖
xml 复制代码
<dependency>
    <groupId>org.jxls</groupId>
    <artifactId>jxls</artifactId>
    <version>2.14.0</version>
</dependency>

<dependency>
    <groupId>org.jxls</groupId>
    <artifactId>jxls-poi</artifactId>
    <version>2.14.0</version>
</dependency>
2.2 设计模板

  创建 excel 模板文件(如 template.xlsx),设计内容格式、样式等。如下示例:

2.3 添加标记

  在刚才创建的模板中添加标注(批注)。

  • 在 A1 单元格 右键 -> 添加批注:jx:area(lastCell = "H4"),其中 lastCell="H4" 表示当前 excel 表格覆盖区域为 A1:H4(并非表示实际数据覆盖区域,而是指模板内容覆盖区域)。
  • 在 C2 单元格键入 ${name},name 为 名称 对应数据的 key。
  • 在 G2 单元格键入 ${time},time 为 时间 对象数据的 key。
  • 在 A4 单元格添加批注:jx:each(items="employees" var="item" lastCell="D4"),其中 items 表示列表数据集所对应 key,var 表示循环内的当前元素,lastCell 表示当前列表的模板范围。
  • 在 A4、B4、C4、D4 单元格依次键入 i t e m . i n d e x 、 {item.index}、 item.index、{item.nickname}、 i t e m . n a m e 、 {item.name}、 item.name、{item.region}。

  最终模板将如下:

2.4 准备数据

  在业务逻辑代码中准备数据,如下:

java 复制代码
List<Employee> employees = new ArrayList<>();   // Employee 类较简单 此处略过
employees.add(Employee.builder().index(1).nickname("影流之主").name("劫").region("艾欧尼亚").build());
employees.add(Employee.builder().index(2).nickname("潮汐海灵").name("菲兹").region("比尔吉沃特").build());
employees.add(Employee.builder().index(3).nickname("九尾妖狐").name("阿狸").region("艾欧尼亚").build());

Context context = new Context();   // JXLS 上下文对象 使用该对象将数据与模板绑定
context.putVar("name", "吧啦吧啦");
context.putVar("time", "2025-05-21");
context.putVar("employees", employees);
2.5 生成 excel
java 复制代码
String template = "/template/template.xlsx";   // 模板文件位于 resources/template/ 目录下
String excel = "C:\\Users\\Lenovo\\Desktop\\test.xlsx";
try (InputStream is = ExportTest.class.getResourceAsStream(template);
     OutputStream os = Files.newOutputStream(Paths.get(excel))) {
    JxlsHelper.getInstance()
        .processTemplate(is, os, context);   // 传入 输入流、输出流、上下文对象 生成 excel 文件
} catch (IOException e) {
    throw new RuntimeException("导出Excel失败", e);
}

  生成结果如下:

3 核心特性

  JXLS 基于模板的核心特性包括 XLS 区域、单数据绑定、循环、条件判断、分组与汇总、公式支持、多工作表、图像插入、单元格样式、合并单元格等。

3.1 XLS 区域

  XLS Area 是指 excel 模板中需要转换的矩形区域,即模板当前工作表中模板内容所占区域,实际上是指模板内容中以最长行和最高列为边的矩形区域。以 2 章节中模板为例,其区域模板语法为:

复制代码
jx:area(lastCell = "H4")

其中 lastCell 即表示模板内容矩形区域,即 H 列与第 4 行所对应的单元格 H4。

3.2 单数据绑定

  模板中独立数据的渲染,可直接在单元格中使用 ${name} 方式当作占位符,其中 name 表示当前单元格对应数据的 key,其支持对象的多层嵌套(即渲染时可从潜嵌套的对象中获取到属性值)。如 e m p l o y e e . r e g i o n 、 {employee.region}、 employee.region、{aaa.bbb.ccc.fieldName}。

3.3 循环

  循环操作对应模板语法的命令为 each, 其用来遍历集合进行数据渲染,同时可进行过滤、分组、排序、枚举值替换等操作。其命令属性如下:

  • items:要遍历的数据集的变量名称。

  • var:循环内的元素变量名称。

  • lastCell:当前列表的模板范围,即当前列表模板区域的最后一个单元格。

  • select :其核心作用是过滤或筛选传入的集合,可通过集合中元素自身属性或上下文其它变量,动态决定哪些元素应该出现再最终的 excel 中。其值为返回一个布尔值的 JEXL 表达式。可在 JEXL 表达式中访问元素属性、JXLS Context 上下文中的其它变量、JEXL 支持的运算符和函数等。

      假设已有类 Employee 拥有 index(序号)、nickname(称号)、name(名称)、region(所属地区)、attackPower(普通攻击力)属性和 isAssAssin() 方法(该方法返回布尔值),示例如下:

    • 基于元素属性过滤(单一条件):

      复制代码
      // 只渲染普通攻击力 > 50 的英雄
      jx:each(items="employees" var="item" lastCell="D4" select="item.attackPower > 50")
    • 基于元素属性过滤(组合条件):

      复制代码
      // 只渲染所属地区为艾欧尼亚 且 普通攻击力 > 50 的英雄
      jx:each(items="employees" var="item" lastCell="D4" select="item.region == 'Ionia' && item.attackPower > 50")
    • 基于元素属性过滤(包含关系 in):

      复制代码
      // 只渲染所属地区属于 艾欧尼亚 或 诺克萨斯 的英雄
      jx:each(items="employees" var="item" lastCell="D4" select="item.region in ['Ionia', 'Noxus']")
    • 基于元素方法过滤:

      复制代码
      // 只渲染刺客类英雄 
      // Employee 类中的 isAssassin() 返回值表示其是否为刺客 
      // 则 JEXL 在解析表达式时会将 item.assassin 解析为调用 item.isAssassin() 方法
      jx:each(items="employees" var="item" lastCell="D4" select="item.assassin")
    • 结合 Context 上下文变量动态过滤:

      复制代码
      // 只渲染所属地区为艾欧尼亚 且 普通攻击力 > 50 的英雄
      // 其中变量 region 和 attackPower 为上下文对象 Context 中的动态值
      Context context = new Context();
      context.putVar("region", "Ionia");
      context.putVar("attackPower", 50);
      context.putVar("employees", employees);
      jx:each(items="employees" var="item" lastCell="D4" select="item.region == region && item.attackPower > attackPower")
    • 注意事项:

      • 空指针安全:JEXL 表达式中可访问嵌套属性,但需注意空指针问题。如 item.aaa.bbb.fieldName,若 aaa 为 null 则会出现空指针异常。推荐使用安全操作符 ?. ,如 item.aaa?.bbb?.fieldName,若 aaa 为 null,则整个表达式为 null,null 在布尔值上下文中通常被视为 false。
      • 性能问题:建议在 java 业务代码中对数据进行过滤。
      • 与 groupBy 使用:select 过滤发生在 groupBy 分组之前,若有在分组后进行二次过滤的需求,则可结合 jx:if 指令在分组后进行二次过滤。
      • 值比较:使用 == 或 != 比较值时,需确保双方类型一致,如都是 String 或 Integer。
      • 转义特殊字符:对于 xml 相关的特殊字符(如 < > 等),需要进行转义。
  • groupBy:用于实现数据分组和分组汇总。可将数据按指定属性分组,并在分组前后添加自定义内容(如分组标题、小计等),同时可通过 groupOrder 指定组内排序方式(如 desc 或 asc)。示例如下:

    • 模板:

      复制代码
      A1 批注:jx:area(lastCell = "G5")
      A3 批注:jx:each(items="employees" var="item" lastCell="E5" groupBy="region" groupOrder="desc")
      A5 批注:jx:each(items="item.items" var="e" lastCell="E5")
    • 数据:

      java 复制代码
      List<Employee> employees = new ArrayList<>();
      employees.add(Employee.builder().index(1).nickname("影流之主").name("劫").region("艾欧尼亚").attackPower(66).build());
      employees.add(Employee.builder().index(2).nickname("潮汐海灵").name("菲兹").region("比尔吉沃特").attackPower(58).build());
      employees.add(Employee.builder().index(3).nickname("九尾妖狐").name("阿狸").region("艾欧尼亚").attackPower(53).build());
      
      Context context = new Context();
      context.putVar("name", "吧啦吧啦");
      context.putVar("time", "2025-05-21");
      context.putVar("employees", employees);
    • 结果:

  • orderBy:根据属性进行排序,且可根据多个属性排序,用逗号隔开。如 orderBy="region desc, attackPower asc"。

3.4 条件判断

  jx:if 命令,用来根据条件表达式显示/隐藏单元格。如:jx:if(condition="e.attackPower > 60", lastCell="E4", areas="A4:E4","A5:D5"),其中 condition 表示条件表达式,areas 为区域数组,数组第一个元素指定条件为真时要显示的区域,数组第二个元素指定条件为假时要显示的区域。示例如下:

  • 模板:

    复制代码
    A1 批注:jx:area(lastCell = "G5")
    // 该条件表示 若 attackPower > 60 则不渲染
    A4 批注:jx:each(items="employees" var="e" lastCell="E4")
            jx:if(condition="e.attackPower > 60", lastCell="E4", areas=["A4:E4","A5:D5"])
  • 结果:

3.5 公式支持

  JXLS 通过 evaluateFormulas 属性控制是否在模板中启用 excel 公式。其默认为 true,表示启用,但启用公式会消耗一定性能,故,若实际无公式使用需求,则可将其关闭。

java 复制代码
JxlsHelper.getInstance()
    .setEvaluateFormulas(false)   // 关闭公式支持
    .processTemplate(is, os, context);

  同时支持参数化公式,即在公式中使用变量。当在公式中使用变量时,需要使用 \[ \] 符号将公式包裹,且公式那的变量需要通过 {} 标记。如:

复制代码
$[SUM(E4) * ${param}]   // 其中 param 参数为 Context 上下文变量
3.6 多工作表

  可通过 jx:each 命令和 multisheet 属性来动态创建多工作表的 excel。其中 multisheet 属性表示工作表列表,其元素中应包含每个工作表的名称和数据。示例如下:

  • 模板:

    复制代码
    A1 批注:jx:area(lastCell = "G4")
            jx:each(items="regions", var="region", multisheet="region.region", lastCell="G4")
    A4 批注:jx:each(items="region.employees" var="e" lastCell="E4")
  • 数据:

    java 复制代码
    List<Region> regions = new ArrayList<>();
    
    List<Employee> employees = new ArrayList<>();
    employees.add(Employee.builder().index(1).nickname("影流之主").name("劫").region("艾欧尼亚").attackPower(66).build());
    employees.add(Employee.builder().index(2).nickname("刀锋舞者").name("艾瑞莉娅").region("艾欧尼亚").attackPower(65).build());
    employees.add(Employee.builder().index(3).nickname("九尾妖狐").name("阿狸").region("艾欧尼亚").attackPower(53).build());
    regions.add(Region.builder().region("艾欧尼亚").employees(employees).build());
    
    List<Employee> employees1 = new ArrayList<>();
    employees1.add(Employee.builder().index(1).nickname("潮汐海灵").name("菲兹").region("比尔吉沃特").attackPower(58).build());
    employees1.add(Employee.builder().index(2).nickname("卡牌大师").name("崔斯特").region("比尔吉沃特").attackPower(10).build());
    regions.add(Region.builder().region("比尔吉沃特").employees(employees1).build());
    
    Context context = new Context();
    context.putVar("regions", regions);
    context.putVar("time", "2025-05-21");
  • 结果:

3.7 图像插入

  可通过 jx:image 命令渲染图像。如 jx:image(lastCell="E16" src="dafei" imageType="PNG"),其中 lastCell 表示图像大小(即图像所占单元格区域),src 表示图像文件的字节数组,imagedType 表示图像类型(支持通过 Context 上下文动态传入)。示例如下:

  • 模板:

    复制代码
    // 在图像起始位置单元格(此例中为 A1)插入批注
    jx:image(lastCell="D8" src="dafei" imageType="PNG")
  • 数据:

    java 复制代码
    // 图像文件位于 resources/template/ 目录下 读取文件并将其转为 byte[]
    InputStream dafeiIs = ExportTest.class.getResourceAsStream("/template/xiaodafei.png");
    byte[] dafeiBytes = IOUtils.toByteArray(dafeiIs);
    context.putVar("dafei", dafeiBytes);
  • 结果:

3.8 合并单元格

  可通过 jx:mergeCells 命令合并单元格。

复制代码
jx:mergeCells(cols="3", rows="3", minCols="1", minRows="1", lastCell="D4")
cols: 要合并的列数
rows: 要合并的行数
minCols: 要合并的最小列数
minRows: 要合并的最小行数
lastCell: 要合并的单元格范围
注:动态变化值则可通过 Context 上下文传入
  • 示例一:

    • 模板:

      复制代码
      A1 批注:jx:area(lastCell = "E3")
      A2 批注:jx:mergeCells(cols="1", rows="employeeNumber", minCols="1", minRows="1", lastCell="A3")
      B3 批注:jx:each(items="employees" var="item" lastCell="E3")
    • 数据:

      java 复制代码
      List<Employee> employees = new ArrayList<>();
      employees.add(Employee.builder().index(1).nickname("影流之主").name("劫").region("艾欧尼亚").attackPower(66).build());
      employees.add(Employee.builder().index(2).nickname("潮汐海灵").name("菲兹").region("比尔吉沃特").attackPower(58).build());
      employees.add(Employee.builder().index(3).nickname("九尾妖狐").name("阿狸").region("艾欧尼亚").attackPower(53).build());
      
      Context context = new Context();
      context.putVar("employees", employees);
      context.putVar("employeeNumber", employees.size() + 1);   // 加 1 指表头占一行
    • 结果:

  • 示例二:

    • 模板:

      复制代码
      A1 批注:jx:area(lastCell = "F5")
      A2 批注:jx:mergeCells(cols="1", rows="employeeNumber", minCols="1", minRows="2", lastCell="A5")
      B2 批注:jx:mergeCells(cols="1", rows="assassinNumber", minCols="1", minRows="1", lastCell="B3")
      C3 批注:jx:each(items="assassins" var="item" lastCell="F3")
      B4 批注:jx:mergeCells(cols="1", rows="warriorNumber", minCols="1", minRows="1", lastCell="B5")
      C5 批注:jx:each(items="warriors" var="item" lastCell="F5")
    • 数据:

      java 复制代码
      List<Employee> assassins = new ArrayList<>();
      assassins.add(Employee.builder().index(1).nickname("影流之主").name("劫").region("艾欧尼亚").attackPower(66).build());
      assassins.add(Employee.builder().index(2).nickname("潮汐海灵").name("菲兹").region("比尔吉沃特").attackPower(58).build());
      assassins.add(Employee.builder().index(3).nickname("九尾妖狐").name("阿狸").region("艾欧尼亚").attackPower(53).build());
      
      List<Employee> warriors = new ArrayList<>();
      warriors.add(Employee.builder().index(1).nickname("刀锋舞者").name("艾瑞丽娅").region("艾欧尼亚").build());
      warriors.add(Employee.builder().index(2).nickname("放逐之刃").name("锐雯").region("诺克萨斯").build());
      
      Context context = new Context();
      context.putVar("assassins", assassins);
      context.putVar("assassinNumber", assassins.size() + 1);
      context.putVar("warriors", warriors);
      context.putVar("warriorNumber", warriors.size() + 1);
      context.putVar("employeeNumber", assassins.size() + warriors.size() + 2);
    • 结果:

3.9 单元格样式

  JXLS 模板支持单元格样式,即模板中的样式最终会渲染到生成的 excel 文件中。

4 课后小知识

4.1 图像 svg 转 png

  使用 batik 库将 Base64 格式的 svg 图像转成 png,若 Base64 是压缩后的数据,则需要解压缩。

xml 复制代码
<dependency>
    <groupId>org.apache.xmlgraphics</groupId>
    <artifactId>batik-transcoder</artifactId>
    <version>1.19</version>
</dependency>

<dependency>
    <groupId>org.apache.xmlgraphics</groupId>
    <artifactId>batik-codec</artifactId>
    <version>1.19</version>
</dependency>
java 复制代码
String svgBase64 = "";
byte[] decode = Base64.getDecoder().decode(svgBase64);

try (ByteArrayInputStream is = new ByteArrayInputStream(decode);
     ByteArrayOutputStream os = new ByteArrayOutputStream();
     GZIPInputStream zipIs = new GZIPInputStream(is)) {
    PNGTranscoder transcoder = new PNGTranscoder();
    TranscoderInput input = new TranscoderInput(zipIs);
    TranscoderOutput output = new TranscoderOutput(os);
    transcoder.transcode(input, output);
    return os.toByteArray();
} catch (Exception e) {
    e.printStackTrace();
}
相关推荐
xcl09255 小时前
北京24小时自助健身房系统软件开发实战:从架构到部署全流程指南
java·spring boot·架构
一 乐7 小时前
动漫书销售商城|基于springboot + vue动漫书销售商城(源码+数据库+文档)
java·数据库·vue.js·spring boot·毕业设计
卓怡学长7 小时前
w214基于jsp知道特产网
java·intellij-idea
步行cgn7 小时前
Spring 注解使用详解
java·spring
一条小小yu8 小时前
Spring IoC的理解
java·后端·spring
落魄实习生8 小时前
Agent Scope Java 2.x 系列【7】工具使用
java·开发语言·ai
旺仔学长 哈哈8 小时前
springboot钓鱼爱好者交流平台APP设计与实现
java·spring boot·mysql·充电桩管理系统
自强的小白10 小时前
核心功能(Service接口)
java·mybatis
乌暮11 小时前
深入理解 Java 泛型:把「万能盒子」用对、用稳
java·开发语言·后端·学习