應邀寫的一篇文章:Java項目中利用Freemarker模板引擎導出--生成Word文檔
在項目中難免和各種數據報表打交道,如導出XX申請表,登記表,推薦表之類。就可以通過現有信息導出Word文檔。基於Java語言來導出Word文檔的方式也有很多種,如Jacob,Apache POI,Freemarker,PageOffice,java2word 等等。。。。
在這裏將通過Freemarker這個模板引擎來實現導出 Word,項目不限於Swing,SSH,SSM,Spring Boot 之類的。。。。。。。。。。
Freemarker介紹
首先說一下Freemarker是個什麼東西:
FreeMarker是一款模板引擎: 即一種基於模板和要改變的數據, 並用來生成輸出文本(HTML網頁、電子郵件、配置文件、源代碼等)的通用工具。 它不是面向最終用戶的,而是一個Java類庫,是一款程序員可以嵌入他們所開發產品的組件。
FreeMarker是免費的,基於Apache許可證2.0版本發佈。其模板編寫爲FreeMarker Template Language(FTL)
,屬於簡單、專用的語言。需要準備數據在真實編程語言中來顯示,比如數據庫查詢和業務運算, 之後模板顯示已經準備好的數據。在模板中,主要用於如何展現數據, 而在模板之外注意於要展示什麼數據 [1] 。
------百度百科
總的來說:模板 + 數據模型 = 輸出
看不懂也沒關係,看一個簡單的例子:通常我們在Java Web開發的時候,實現一個JSP頁面的輸出需要編寫一個JSP文件《xxxx.jsp》,實際上這個JSP文件主要包含了兩部分內容:
HTML+CSS+JavaScript構成的頁面框架 + 用來動態顯示數據庫數據的Java代碼(一般來說直接寫Java代碼是非常不美觀且修改複雜的事情,不符合MVC開發模式
,所以會用
JSTL標籤來代替直接寫Java代碼
)
在Freemarker
中也有一套類似於JSTL
的標籤,其實簡單來說freemarker
就是用來代替jsp
來做處理,因爲我們如果用JSP
的話,是需要WEB
容器的,如tomcat,JSP
在第一次執行的時候會被編譯轉換成Servlet
類,之後的每次修改都要編譯和轉換。而FreeMarker模板技術並不存在編譯和轉換的問題,並且與容器無關。我們在非web
項目下也沒必要再引入一個web
容器了。
示例用的項目搭建
下面進入正題:
首先創建一個項目,在這裏的項目可以爲Java swing,Javaweb的SSM,Springboot等項目都是沒問題的。我還是直接用SpringBoot項目實現這個功能了。
首先用IDEA創建一個Spring Boot項目
用其他IDE的也可以在https://start.spring.io網站上面直接下載一個springboot項目
下一步填完信息,這裏我用默認的信息了
開始選擇所需要的插件依賴,也就是jar包,都是通過maven管理的,上圖有說明
1.
2. 選擇Spring Web,就是SpringMVC
3. 選擇用哪個模板引擎,這裏用Freemarker
4. 選擇數據庫框架,這裏用mybaits+mysql
5. 下一步,選擇項目路徑,點擊完成就可以了
Freemarker主要依賴的jar包是
# Springboot 提供的
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
# 也可以選擇其他版本如:
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
# 網址:
https://mvnrepository.com/artifact/org.freemarker/freemarker
FTL模板製作
項目搭建好之後,開始製作FTL模板,這次選擇一個XXX推薦表來實現:
如Word文檔:
開始製作模板:
首先,我們需要在模板上面添加佔位符,${XXXXX}。這裏全用了拼音簡寫
添加好佔位符之後,將該Word文檔另存爲XML格式的文件
修改一下文件名稱,保存即得到一個XML文件
打開觀察一下,正常
接下來,利用文本編輯器打開這個文件,如Notepad++、Sublime Text、EditPlus、UltraEdit什麼都行,我這裏用VSCode。如圖
直接搜索前面添加的佔位符${xxxx},這裏可能會出現一些問題,如符號和花括號分離,這時候就得對它進行修改,恢復原樣
修改後
等檢查一遍沒問題之後呢,就修改完成了,開始進入下一步修改文件後綴
代碼編寫
接下來開始代碼編寫部分,一般來說這裏都是用數據庫的數據作爲導出數據。但由於麻煩就不寫數據庫部分了,在這裏直接用提交表單的數據來進行導出。
- 回到項目上,新建一個實體類,TableData.java ,將佔位符標識作爲類屬性,生成get、set方法。。。。。
發現院系YX和郵箱YX重複了,修改郵箱爲DZYX
2. 新建簡單的測試頁面
3. 編寫導出工具類
首先將實體類封裝爲導出數據所需的map,創建數據模型
**注意:
**在這裏直接用的是封裝實體類,所以在模板裏面也需要改一下佔位符,在前面加上對象名${DZYX}
變爲${tableData.DZYX}
,用文本編輯器一鍵搜索${
替換即可
編寫方法,創建freeMarker配置實例,創建數據模型,加載模板,輸出
/**
* 導出Word文件
*
* @param tableData
* @param request
* @param response
* @throws Exception
*/
public InputStreamSource exportToWord(TableData tableData, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 糾正編碼
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("utf-8");
// ** 初始化配置文件**//*
Configuration configuration = new Configuration();
// ** 設置編碼 **//*
configuration.setDefaultEncoding("utf-8");
// ** ftl文件的路徑**//*
String fileDirectory = "D:\\"; //將模板文件放到了D盤下
// 加載模板文件
configuration.setDirectoryForTemplateLoading(new File(fileDirectory));
// 加載模板,通過Word轉XML文件轉換過來的
Template template = configuration.getTemplate("FreemarkerTest.ftl");
// 準備數據
Map<String, Object> dataMap = data(tableData);
// 指定輸出word文件的路徑
// 調用工具類WordUtils的createDoc方法生成Word文檔
InputStreamSource file = UtilTest.createDoc(dataMap, template);
InputStream fin = file.getInputStream();
ServletOutputStream out;
response.setContentType("application/msword");
// 設置瀏覽器以下載的方式處理該文件
response.setHeader("content-disposition", "attachment;filename=document.doc");
out = response.getOutputStream();
// 緩衝區
byte[] buffer = new byte[512];
int bytesToRead;
// 通過循環將讀入的Word文件的內容輸出到瀏覽器中
while ((bytesToRead = fin.read(buffer)) != -1) {
out.write(buffer, 0, bytesToRead);
}
fin.close();
if (out != null) {
out.close();
}
return file;
}
創建word文件
/**
* 創建word文件
*
* @param dataMap
* @param template
* @return
* @throws TemplateException
* @throws IOException
*/
public static InputStreamSource createDoc(Map<String, Object> dataMap, Template template)
throws TemplateException, IOException {
//生成隨機的合同名稱
StringWriter out1 = new StringWriter();
Writer out = new BufferedWriter(out1, 10240);
//將數據輸出到模板
template.process(dataMap, out);
out.close();
out1.close();
return new ByteArrayResource(out1.toString().getBytes(StandardCharsets.UTF_8));
}
- 編寫控制層
運行項目,跳到測試頁面,填寫相應信息
提交表單,會彈出下載框
將文件下載到本地,打開,可以看到已經填充完成,
至此,基於Java和freemarker實現的Word文件導出功能已經實現了
再回顧一下步驟:
-
編輯好格式的Word文檔 1份
-
將Word文檔裏需要填充的地方加上佔位符
${xxxx}
-
將編輯好佔位符的文檔另存爲XML格式
Word 2003 XML文檔
,並重命名,用英文命名 -
利用文本編輯器打開該XML文件檢查,搜索第二步編輯的佔位符,遇到
$和 { }
分離的情況則進行修改。檢查完畢後保存退出。 -
將檢查完成的XML文件修改後綴名爲 xxx.ftl
-
模板編輯完成
-
建立Java項目,引入jar包
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
- 編寫對應數據的實體類
- 編寫測試頁面
- 編寫導出的工具類(
核心
),並修改模板對應的佔位符,有需要的話 - 編寫控制層
- 測試
代碼
項目代碼–工具類
package com.hh.onlinelearning.util;
import com.hh.onlinelearning.entity.TableData;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamSource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class UtilTest {
/**
* 導出Word文件
*
* @param tableData
* @param request
* @param response
* @throws Exception
*/
public InputStreamSource exportToWord(TableData tableData, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 糾正編碼
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("utf-8");
// ** 初始化配置文件**//*
Configuration configuration = new Configuration();
// ** 設置編碼 **//*
configuration.setDefaultEncoding("utf-8");
// ** ftl文件的路徑**//*
String fileDirectory = "D:\\";
// 加載模板文件
configuration.setDirectoryForTemplateLoading(new File(fileDirectory));
// 加載模板,通過Word轉XML文件轉換過來的
Template template = configuration.getTemplate("FreemarkerTest.ftl");
// 準備數據
Map<String, Object> dataMap = data(tableData);
// 指定輸出word文件的路徑
// 調用工具類WordUtils的createDoc方法生成Word文檔
InputStreamSource file = UtilTest.createDoc(dataMap, template);
InputStream fin = file.getInputStream();
ServletOutputStream out;
response.setContentType("application/msword");
// 設置瀏覽器以下載的方式處理該文件
response.setHeader("content-disposition", "attachment;filename=document.doc");
out = response.getOutputStream();
// 緩衝區
byte[] buffer = new byte[512];
int bytesToRead;
// 通過循環將讀入的Word文件的內容輸出到瀏覽器中
while ((bytesToRead = fin.read(buffer)) != -1) {
out.write(buffer, 0, bytesToRead);
}
fin.close();
if (out != null) {
out.close();
}
return file;
}
/**
* 構造數據,key-value
*
* @param tableData
* @return
*/
public Map<String, Object> data(TableData tableData) {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("tableData", tableData);
return dataMap;
}
/**
* 創建word文件
*
* @param dataMap
* @param template
* @return
* @throws TemplateException
* @throws IOException
*/
public static InputStreamSource createDoc(Map<String, Object> dataMap, Template template)
throws TemplateException, IOException {
//生成隨機的合同名稱
StringWriter out1 = new StringWriter();
Writer out = new BufferedWriter(out1, 10240);
//將數據輸出到模板
template.process(dataMap, out);
out.close();
out1.close();
return new ByteArrayResource(out1.toString().getBytes(StandardCharsets.UTF_8));
}
}
控制層代碼:
/**
* 提交表單,下載Word文件
* @param tableData
* @param request
* @param response
* @return
* @throws Exception
*/
@PostMapping("/export")
public String a(TableData tableData, HttpServletRequest request, HttpServletResponse response) throws Exception {
UtilTest u = new UtilTest();
u.exportToWord(tableData,request,response);
return "";
}
/**
* 跳轉到表單填寫頁
* @return
*/
@GetMapping("/")
public String index() {
return "/upload";
}
實體類代碼:
package com.hh.onlinelearning.entity;
/**
* 對應Word文檔的佔位符,實體類
*/
public class TableData {
private int id;
private String XM;
private String XB;
private String MZ;
private String CSNY;
private String ZZMM;
private String YX;
private String ZY;
private String SYD;
private String XL;
private String XW;
private String DH;
private String SG;
private String TZ;
private String DZYX;
private String SLZ;
private String SLY;
private String LXDZ;
private String WY;
private String JSJ;
private String JYYX;
private String AH;
private String JWJD;
private String SHSJ;
private String RZQK;
private String HJQK;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getXM() {
return XM;
}
public void setXM(String XM) {
this.XM = XM;
}
public String getXB() {
return XB;
}
public void setXB(String XB) {
this.XB = XB;
}
public String getMZ() {
return MZ;
}
public void setMZ(String MZ) {
this.MZ = MZ;
}
public String getCSNY() {
return CSNY;
}
public void setCSNY(String CSNY) {
this.CSNY = CSNY;
}
public String getZZMM() {
return ZZMM;
}
public void setZZMM(String ZZMM) {
this.ZZMM = ZZMM;
}
public String getYX() {
return YX;
}
public void setYX(String YX) {
this.YX = YX;
}
public String getZY() {
return ZY;
}
public void setZY(String ZY) {
this.ZY = ZY;
}
public String getSYD() {
return SYD;
}
public void setSYD(String SYD) {
this.SYD = SYD;
}
public String getXL() {
return XL;
}
public void setXL(String XL) {
this.XL = XL;
}
public String getXW() {
return XW;
}
public void setXW(String XW) {
this.XW = XW;
}
public String getDH() {
return DH;
}
public void setDH(String DH) {
this.DH = DH;
}
public String getSG() {
return SG;
}
public void setSG(String SG) {
this.SG = SG;
}
public String getTZ() {
return TZ;
}
public void setTZ(String TZ) {
this.TZ = TZ;
}
public String getDZYX() {
return DZYX;
}
public void setDZYX(String DZYX) {
this.DZYX = DZYX;
}
public String getSLZ() {
return SLZ;
}
public void setSLZ(String SLZ) {
this.SLZ = SLZ;
}
public String getSLY() {
return SLY;
}
public void setSLY(String SLY) {
this.SLY = SLY;
}
public String getLXDZ() {
return LXDZ;
}
public void setLXDZ(String LXDZ) {
this.LXDZ = LXDZ;
}
public String getWY() {
return WY;
}
public void setWY(String WY) {
this.WY = WY;
}
public String getJSJ() {
return JSJ;
}
public void setJSJ(String JSJ) {
this.JSJ = JSJ;
}
public String getJYYX() {
return JYYX;
}
public void setJYYX(String JYYX) {
this.JYYX = JYYX;
}
public String getAH() {
return AH;
}
public void setAH(String AH) {
this.AH = AH;
}
public String getJWJD() {
return JWJD;
}
public void setJWJD(String JWJD) {
this.JWJD = JWJD;
}
public String getSHSJ() {
return SHSJ;
}
public void setSHSJ(String SHSJ) {
this.SHSJ = SHSJ;
}
public String getRZQK() {
return RZQK;
}
public void setRZQK(String RZQK) {
this.RZQK = RZQK;
}
public String getHJQK() {
return HJQK;
}
public void setHJQK(String HJQK) {
this.HJQK = HJQK;
}
}
HTML測試頁:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>測試</title>
</head>
<body>
<h2>測試</h2>
<!--表單-start-->
<form id="form" method="post" action="/export">
<input name="XM" type="text" placeholder="姓名" >
<input name="XB" type="text" placeholder="性別">
<input name="MZ" type="text" placeholder="民族">
<input name="CSNY" type="text" placeholder="出生年月">
<input name="ZZMM" type="text" placeholder="政治面貌">
<input name="YX" type="text" placeholder="院系">
<input name="ZY" type="text" placeholder="專業">
<input name="SYD" type="text" placeholder="生源地">
<input name="XL" type="text" placeholder="學歷">
<input name="XW" type="text" placeholder="學位">
<input name="DH" type="text" placeholder="電 話">
<input name="SG" type="text" placeholder="身高">
<input name="TZ" type="text" placeholder="體重">
<input name="DZYX" type="text" placeholder="E-mail">
<input name="SLZ" type="text" placeholder="視力Z">
<input name="SLY" type="text" placeholder="視力Y">
<input name="LXDZ" type="text" placeholder="聯繫地址">
<input name="WY" type="text" placeholder="外語程度">
<input name="JSJ" type="text" placeholder="計算機">
<input name="JYYX" type="text" placeholder="就業意向">
<input name="AH" type="text" placeholder="愛好特長">
<input name="JWJD" type="text" placeholder="自我鑑定">
<input name="SHSJ" type="text" placeholder="社會實踐">
<input name="RZQK" type="text" placeholder="任職情況">
<input name="HJQK" type="text" placeholder="獲獎情況">
<input id="upload" type="submit" value="提交">
</form>
<!--表單-end-->
</body>
</html>
freemarker 各個版本的jar包:Maven倉庫