zoukankan      html  css  js  c++  java
  • 如何通过 Freemark 优雅地生成那些花里胡哨的复杂样式 Excel 文件?

    欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 高级架构师吐血总结的 《Java 核心知识整理&面试.pdf》资源链接!!

    个人网站: https://www.exception.site/essay/how-to-create-complex-style-excel-with-freemark

    freemark生成复杂样式excel

    一、背景

    小哈最近这段时间开始负责一个新的产品:下载中心。啥玩意这是?

    产品的目的其实就是统一管控各业务组文件下载功能(包括一些海量数据的导出,文件合并上传等),项目组不用自己再去实现各式各样的文件(PDF, Word, Excel)生成, 统一对接下载中心,由下载中心统一完成文件的生成、合并、上传、下载流程。

    问题来了,这里面包括一些复杂文件的生成,如带有复杂样式的 Excel 文件,比如下面这个样子的:

    复杂Excel

    这种复杂样式的 Excel, 如果说放到各个业务线去实现还是好办的,因为站在各个业务组的角度,场景变化不会太多,按照文件格式,代码写死即可。

    但是站在下载中心的角度,因为需要对接各个业务中心,每个业务中心生成的样式都不一样,不可能每个业务组接进来,我都得定制的写一套生成代码吧!这显然也不合常理!

    那么,有没有什么一劳永逸的办法呢?答案是肯定的!

    二、实现思路

    要说实现方式,你的脑海里可能第一会想到传统的 Apache poi,jxl ,亦或者是阿里出品 EasyExcel 等等。

    PS: 关于阿里的 EasyExcel, 小哈之前有分享过 ,没看过的小伙伴们,可以看下《7 行代码优雅地实现 Excel 文件生成&下载功能》

    对于这种复杂样式,要是用 Apache poi, jxl, 阿里 EasyExcel 去实现,不可避免的,代码肯定会非常繁琐。

    有没有啥优雅(偷懒的)的方式呢?

    其实我们可以通过视图引擎 Freemark、Velocity 来帮我们生成复杂样式 Excel 文件,无需关心花里胡哨的复杂样式,只关注于填充数据即可。接下来,我们以 Freemark 作为示例来讲解,如何生成这个复杂样式的 Excel 文件。

    拓展阅读: 什么是 Freemark ?

    FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。

    其实,对于Java 后端来说,它更常被用来服务端动态渲染 html 页面返回给浏览器。前些年还比较火热,近些年因为前后端分离的火热,也开始慢慢淡出视野了。

    三、快速上手

    3.1 添加依赖

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>
    

    注意: 小哈这里基于 Spring Boot 写的测试代码,版本号可以无需指定,否则,你需要手动指定好版本号。

    3.2 导出 xml 模板文件

    首先,将复杂样式的 Excel 文件另存为 .xml 视图模板,如下图所示:

    打开 xml 模板文件,可以清晰的看到里面定义了各种节点,节点描述了整个 Excel 的样式结构, 如下图所示:

    3.3 填充占位符

    再回过头来看下之前那个复杂 Excel 文件, 观察一下哪些单元格的值需要动态设置:

    图中用红色特意标注出来了。

    在刚刚另存为的 xml 模板文件中填写 freemark 表达式,考虑到这里只是个示例 Demo, 仅仅选取几个示例单元格来填写占位符,如下所示:

    订单标题:

    其他需要动态填充的单元格:

    PS: xml 文件中,<Row> 节点代表一行,<Cell>代表一个单元格。

    在需要动态填充数据的地方,加上相关 freemark 表达式,如 ${commodity.name!},如下所示:

    <Row ss:AutoFitHeight="0" ss:Height="54">
        <Cell ss:StyleID="s18"><Data ss:Type="String">1</Data></Cell>
        <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.name!}</Data></Cell>
        <Cell ss:StyleID="s18"/>
        <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.num!}</Data></Cell>
        <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.num1!}</Data></Cell>
        <Cell ss:StyleID="s18"><Data ss:Type="String">22</Data></Cell>
        <Cell ss:StyleID="s18"><Data ss:Type="String">44</Data></Cell>
        <Cell ss:StyleID="s18"><Data ss:Type="String">55</Data></Cell>
        <Cell ss:StyleID="s18"><Data ss:Type="String">盒</Data></Cell>
        <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.price!}</Data></Cell>
        <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.price2!}</Data></Cell>
        <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.price3!}</Data></Cell>
        <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.timestamp}</Data></Cell>
        <Cell ss:StyleID="s18"><Data ss:Type="String">${commodity.createTime?string('yyyy-MM-dd HH:mm:ss')}</Data></Cell>
        <Cell ss:StyleID="s18"/>
    </Row>
    

    按照服务端数据模型的定义,填写好相应的字段名称,再对照下后台 Commodity 商品类的定义:

    这个商品类中,我们定义了不同类型的字段,如 String、int、Integer、Double、Float、金额类型 BigDecimal、日期类型 Date 等,用以测试对不同数据类型的兼容性。

    确认相关属性字段名无误后,再来看下 freemark 生成 Excel 的核心代码:

    package site.exception.springbootfreemarkexcel;
    
    import freemarker.template.Configuration;
    import freemarker.template.Template;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    import site.exception.springbootfreemarkexcel.entity.Commodity;
    
    import java.io.File;
    import java.io.FileWriter;
    import java.math.BigDecimal;
    import java.util.*;
    /**
     * @author 犬小哈 (微信公众号: 小哈学Java)
     * @site www.exception.site
     * @date 2019/5/21
     * @time 上午10:57
     * @discription
     **/
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class SpringBootFreemarkExcelApplicationTests {
    
    	@Test
    	public void createExcelByFreemark() throws Exception {
    
    		Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
    
    		// 设置模板文件的父目录
    		configuration.setDirectoryForTemplateLoading(new File("/Users/a123123/Work/tmp_files"));
    
    		// 加载模板文件
    		Template template = configuration.getTemplate("/excel_template.xml", "UTF-8");
    
    		// 数据准备,可以是从数据库中查询,这里为了方便演示,手动 new 了
    		Map<String, Object> data = new HashMap<>();
    		data.put("title", "测试标题1");
    
    		List<Commodity> commodities = new ArrayList<>();
    		Commodity commodity = Commodity.builder()
    				.name("name1").num(11).num1(111)
    				.price(new BigDecimal(11.1)).price2(new Double(11.11))
    				.price3(new Float(11.1111))
    				.createTime(new Date())
    				.timestamp(System.currentTimeMillis())
    				.build();
    
    		commodities.add(commodity);
    
    		// 生成 excel 文件
    		template.process(data, new FileWriter("/Users/a123123/Work/tmp_files/excelByFreemark.xls"));
    	}
    }
    

    可以看到生成复杂样式的 Excel 的代码非常简洁。关于每一行代码什么意思,注释已经说得很清楚了,这里就不加以说明了。

    运行单元测试,看下效果:

    完美,在需要填充内容的地方都已经动态设置上了内容。

    四、多行数据如何生成?

    如何做到动态生成多行呢?其实也很简单,重新打开刚刚修改的 xml 模板文件,在需要动态生成多行的地方,添加 freemark 循环表达式即可:

    PS: 关于 Freemark 更多表达式的使用,小伙伴们可以自行在各大搜索引擎中搜索,因为如何使用 Freemark 不是本文关注的重点~

    上图中,我们对后台的 commodities 字段做了循环,所以对应的,后台代码也需要做相关修改:

    我们在 commodities 中添加了两个商品对象。赶快代码跑起来,看看效果!

    别急,还有个地方需要做下修改,不然会报错!!

    找到 <Table> 节点,有个属性叫 ExpandedRowCount, 它定义了表格行的总数,如果数值与实际的行数对应不上的话,会出问题。

    这里我们添加 Freemark 表达式,总行数为商品 commodites 集合的大小加上 16, 注意:16 为除了动态生成的行数外,固定不变的行数大小,小伙伴们如果使用的是不同的 xml 模板,需要自行确认好这个数值的大小。

    修改完了以后,再次运行单元测试,效果如下:

    OK! 大功告成!

    五、局限性

    通过视图解析器来生成 Excel 的确很优雅(偷懒),同时兼具灵活性。但是它同样存在一些局限性!小伙伴们在技术选型时,需要结合实际的业务场景审视它是否适合。

    • 版本问题:

    目前个人测试结果是,在 MAC 系统上仅支持生成 03 版本 Excel, 07 版本存在打不开的情况;

    • 无法写入大批量数据:

    视图引擎生成文件无法往 Excel 里面追加数据,所以仅仅适用于数据量不大的个性化 Excel 生成,否则写入大批量数据时,存在内存溢出(OOM)的情况发生;

    • MAC 系统存在生成的 Excel 文件无法编辑保存的情况:

    小哈在测试中发现,生成 excel 在 MAC 系统上存在编辑后,无法保存的情况;而 Windows 系统 Microsoft Excel 和 WPS 均能够正常编辑保存;

    六、总结

    本文中,小哈给大家介绍了如何通过视图引擎优雅的生成 Excel 文件,演示了相关示例代码,以及它的相关局限性,希望大家看完本文后能够有所收获,下期见哟~

    GitHub 示例代码

    免费分享 | 面试&学习福利资源

    最近在网上发现一个不错的 PDF 资源《Java 核心知识&面试.pdf》分享给大家,不光是面试,学习,你都值得拥有!!!

    获取方式: 关注公众号: 小哈学Java, 后台回复资源,既可免费无套路获取资源链接,下面是目录以及部分截图:

    关注微信公众号【小哈学Java】,回复【资源】,即可免费无套路领取资源链接哦

    关注微信公众号【小哈学Java】,回复【资源】,即可免费无套路领取资源链接哦

    关注微信公众号【小哈学Java】,回复【资源】,即可免费无套路领取资源链接哦

    关注微信公众号【小哈学Java】,回复【资源】,即可免费无套路领取资源链接哦

    关注微信公众号【小哈学Java】,回复【资源】,即可免费无套路领取资源链接哦

    关注微信公众号【小哈学Java】,回复【资源】,即可免费无套路领取资源链接哦

    重要的事情说两遍,关注公众号: 小哈学Java, 后台回复资源,既可免费无套路获取资源链接 !!!

  • 相关阅读:
    二分练习题4 查找最接近的元素 题解
    二分练习题5 二分法求函数的零点 题解
    二分练习题3 查找小于x的最大元素 题解
    二分练习题2 查找大于等于x的最小元素 题解
    二分练习题1 查找元素 题解
    code forces 1176 D. Recover it!
    code forces 1173 B. Nauuo and Chess
    code forces 1173 C. Nauuo and Cards
    吴恩达深度学习课程笔记-15
    吴恩达深度学习课程笔记-14
  • 原文地址:https://www.cnblogs.com/quanxiaoha/p/10990831.html
Copyright © 2011-2022 走看看