歡迎關注我的微信公衆號: 小哈學Java, 文末 分享阿里 P8 高級架構師吐血總結的 《Java 核心知識整理&面試.pdf》資源連接!!我的網站: https://www.exception.site/essay/how-to-create-complex-style-excel-with-freemarkhtml
小哈最近這段時間開始負責一個新的產品:下載中心。啥玩意這是?java
產品的目的其實就是統一管控各業務組文件下載功能(包括一些海量數據的導出,文件合併上傳等),項目組不用本身再去實現各式各樣的文件(PDF, Word, Excel)生成, 統一對接下載中心,由下載中心統一完成文件的生成、合併、上傳、下載流程。git
問題來了,這裏麪包括一些複雜文件的生成,如帶有複雜樣式的 Excel 文件,好比下面這個樣子的:程序員
這種複雜樣式的 Excel, 若是說放到各個業務線去實現仍是好辦的,由於站在各個業務組的角度,場景變化不會太多,按照文件格式,代碼寫死便可。github
可是站在下載中心的角度,由於須要對接各個業務中心,每一個業務中心生成的樣式都不同,不可能每一個業務組接進來,我都得定製的寫一套生成代碼吧!這顯然也不合常理!面試
那麼,有沒有什麼一勞永逸的辦法呢?答案是確定的!spring
要說實現方式,你的腦海裏可能第一會想到傳統的 Apache poi,jxl ,亦或者是阿里出品 EasyExcel 等等。數據庫
PS: 關於阿里的 EasyExcel, 小哈以前有分享過 ,沒看過的小夥伴們,能夠看下《 7 行代碼優雅地實現 Excel 文件生成&下載功能》。
對於這種複雜樣式,要是用 Apache poi, jxl, 阿里 EasyExcel 去實現,不可避免的,代碼確定會很是繁瑣。後端
有沒有啥優雅(偷懶的)的方式呢?瀏覽器
其實咱們能夠經過視圖引擎 Freemark、Velocity 來幫咱們生成複雜樣式 Excel 文件,無需關心花裏胡哨的複雜樣式,只關注於填充數據便可。接下來,咱們以 Freemark 做爲示例來說解,如何生成這個複雜樣式的 Excel 文件。
拓展閱讀: 什麼是 Freemark ?FreeMarker 是一款 模板引擎: 即一種基於模板和要改變的數據, 並用來生成輸出文本(HTML網頁,電子郵件,配置文件,源代碼等)的通用工具。 它不是面向最終用戶的,而是一個Java類庫,是一款程序員能夠嵌入他們所開發產品的組件。
其實,對於Java 後端來講,它更常被用來服務端動態渲染 html 頁面返回給瀏覽器。前些年還比較火熱,近些年由於先後端分離的火熱,也開始慢慢淡出視野了。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency>
注意: 小哈這裏基於 Spring Boot 寫的測試代碼,版本號能夠無需指定,不然,你須要手動指定好版本號。
首先,將複雜樣式的 Excel 文件另存爲 .xml
視圖模板,以下圖所示:
打開 xml 模板文件,能夠清晰的看到裏面定義了各類節點,節點描述了整個 Excel 的樣式結構, 以下圖所示:
再回過頭來看下以前那個複雜 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)的狀況發生;
小哈在測試中發現,生成 excel 在 MAC 系統上存在編輯後,沒法保存的狀況;而 Windows 系統 Microsoft Excel 和 WPS 均可以正常編輯保存;
本文中,小哈給你們介紹瞭如何經過視圖引擎優雅的生成 Excel 文件,演示了相關示例代碼,以及它的相關侷限性,但願你們看完本文後可以有所收穫,下期見喲~
最近在網上發現一個不錯的 PDF 資源《Java 核心知識&面試.pdf》分享給你們,不光是面試,學習,你都值得擁有!!!
獲取方式: 關注公衆號: 小哈學Java, 後臺回覆資源,既可免費無套路獲取資源連接,下面是目錄以及部分截圖:
重要的事情說兩遍,關注公衆號: 小哈學Java, 後臺回覆資源,既可免費無套路獲取資源連接 !!!