Java通過(guò)freemarker生成Word文檔導(dǎo)出的方式詳解
1.制作ftl模板
在Word中制作模板,制作完后以XML格式導(dǎo)出
新建WORD文件,將所需參數(shù)用特殊符號(hào) ${} 配置,如下圖:
注意:freemarker中的占位符是${},例如:這里使用的是【${name}】的形式,那么傳遞的數(shù)據(jù)中就需要有一個(gè)叫做【name】的字段。

導(dǎo)出時(shí)選擇.xml格式導(dǎo)出

用編譯器(notepad++)打開(kāi)看一下動(dòng)態(tài)參數(shù)是否正確,并修改后綴名為ftl
導(dǎo)出XML文件之后,打開(kāi)這個(gè)文件,此時(shí)你會(huì)看到里面都是XML標(biāo)簽,首先格式化一下,這樣看起來(lái)會(huì)舒服些,可以檢查一下你的占位符內(nèi)容是否滿(mǎn)足freemarker語(yǔ)法。因?yàn)橛行r(shí)候,我們導(dǎo)出的XML文件中,可能會(huì)將【${xxx}】分隔成兩行,從而導(dǎo)致占位符失效,所以有時(shí)候需要手動(dòng)修改一下占位符。導(dǎo)出的Word XML文件內(nèi)容大致如下所示:

2.pom文件引入freemarke依賴(lài),安裝依賴(lài)
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>3.java代碼實(shí)現(xiàn)
3.1 將ftl模板移入項(xiàng)目中

3.2 創(chuàng)建Freemarker工具類(lèi)
引入freemarker依賴(lài)之后,就可以使用Freemarker編寫(xiě)一個(gè)工具類(lèi),專(zhuān)門(mén)用于處理文件的導(dǎo)出和數(shù)據(jù)渲染。
import freemarker.template.Configuration;
import freemarker.template.Template;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* @version 1.0.0
* @Date: 2025/2/14 15:05
* @Author SYG
* @Description: Freemarker 工具類(lèi)
*/
public class FreemarkerUtil {
/**
* 使用 Freemarker 生成 Word 文件并導(dǎo)出
* @param templateName 模板文件路徑名稱(chēng)
* @param fileName 生成的文件路徑以及名稱(chēng)
* @param dataModel 填充的數(shù)據(jù)對(duì)象
* @param response HttpServletResponse 對(duì)象
*/
public static void exportWord(String templateName, String fileName, Map<String, Object> dataModel, Object obj, HttpServletResponse response) {
generateFile(templateName, fileName, dataModel, obj, response);
}
/**
* 使用 Freemarker 生成指定文件并導(dǎo)出
* @param templateName 模板文件路徑名稱(chēng)
* @param fileName 生成的文件路徑以及名稱(chēng)
* @param dataModel 填充的數(shù)據(jù)對(duì)象
* @param obj 基準(zhǔn)類(lèi)對(duì)象
* @param response HttpServletResponse 對(duì)象
*/
private static void generateFile(String templateName, String fileName, Map<String, Object> dataModel, Object obj, HttpServletResponse response) {
try {
// 1、創(chuàng)建配置對(duì)象
Configuration config = new Configuration(Configuration.VERSION_2_3_30);
config.setDefaultEncoding("utf-8");
//設(shè)置了模板文件的加載類(lèi)和路徑,使得Freemarker可以從指定的類(lèi)路徑下的/templates目錄中加載模板文件。
config.setClassForTemplateLoading(obj.getClass(), "/templates");
// 2、獲取模板文件
Template template = config.getTemplate(templateName);
// 3、設(shè)置響應(yīng)頭
response.setContentType("application/msword");
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
// 4、創(chuàng)建寫(xiě)入器
Writer writer = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8));
// 5、渲染模板文件
template.process(dataModel, writer);
// 6、關(guān)閉寫(xiě)入器
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}3.3 測(cè)試案例代碼
public class ExportWordDemo {
public static void main(String[] args) {
String templateName = "opinionCollect.ftl";
String fileName = "opinionCollect.docx";
Map<String, Object> dataModel = new HashMap<>();
dataModel.put("orgName", "測(cè)試機(jī)構(gòu)02141151");
dataModel.put("data", "2025-02-14");
// 執(zhí)行導(dǎo)出
FreemarkerUtil.exportWord(templateName, fileName, dataModel,this,response);
}
}案例所使用的ftl文件共有兩個(gè)變量data和orgName,若map集合里只給data或orgName一個(gè)變量賦值(例如我們只給data賦值
dataModel.put("data", "2025-02-14");
),則會(huì)報(bào)錯(cuò):FreeMarker template error:
The following has evaluated to null or missing:==> orgName [in template "payment.ftl" at line 3, column 23752]
為解決當(dāng)前異常,這里有倆種方案:
- 給orgName賦一個(gè)空值,即dataModel.put("orgName", "");
- (推薦)修改模板,根據(jù)數(shù)據(jù)結(jié)構(gòu)字段,將需要填充數(shù)據(jù)的地方,修改為EL表達(dá)式。${()!} 加括號(hào)!號(hào)表示可以為null ,也可以解決上述問(wèn)題,如圖所示:

3.4 解析


config.setClassForTemplateLoading(obj.getClass(), "/templates"); 中的第一個(gè)參數(shù) obj.getClass() 是用于指定模板加載的基準(zhǔn)類(lèi)。具體作用如下:
確定類(lèi)路徑:FreeMarker 根據(jù)傳入的類(lèi)來(lái)確定模板文件所在的類(lèi)路徑。這里使用 obj.getClass() 表示以 obj 所屬類(lèi)的類(lèi)路徑為基準(zhǔn)。加載模板資源:FreeMarker 會(huì)從該類(lèi)所在的類(lèi)路徑下查找并加載模板文件,路徑為 /templates。簡(jiǎn)而言之,第一個(gè)參數(shù)決定了 FreeMarker 在哪里查找模板文件。
此處ftl文件和OpinionCollectController是在同一個(gè)工程目錄下,同一個(gè)target目錄下,所以可以直接傳OpinionCollectController的對(duì)象本身,如果ftl文件和FreemarkerUtil工具類(lèi)在同一個(gè)工程目錄下,我們也可以將config.setClassForTemplateLoading(obj.getClass(), "/templates");改成config.setClassForTemplateLoading(FreemarkerUtil.class, "/templates");
3.5 運(yùn)行代碼
運(yùn)行測(cè)試案例的代碼,可以看到文件正常導(dǎo)出,且變量已經(jīng)被替換

4.使用模板導(dǎo)出word勾選框
實(shí)際業(yè)務(wù)需求需要根據(jù)業(yè)務(wù)勾選上對(duì)應(yīng)的項(xiàng)目類(lèi)型,同時(shí)導(dǎo)出的文件需要有點(diǎn)擊方框自動(dòng)勾選的功能,我們可以先在word模板里實(shí)現(xiàn)該功能,然后導(dǎo)出成xml格式的文件

打開(kāi)xml文件,為了方便查看,我們需要安裝XMLTools的插件
安裝完插件之后,我們可以通過(guò)notepad查看xml文件

如圖所示,找到方框的位置,將方框替換為變量

添加代碼對(duì)test1和test2進(jìn)行賦值
public class ExportWordDemo {
public static void main(String[] args) {
String templateName = "opinionCollect.ftl";
String fileName = "opinionCollect.docx";
Map<String, Object> dataModel = new HashMap<>();
dataModel.put("orgName", "測(cè)試機(jī)構(gòu)02141151");
dataModel.put("data", "2025-02-14");
//? □
dataModel.put("test1", "□");
dataModel.put("test2", "?");
// 執(zhí)行導(dǎo)出
FreemarkerUtil.exportWord(templateName, fileName, dataModel,this,response);
}
}執(zhí)行代碼我們可以看到導(dǎo)出的文件如下圖,同時(shí)方框也可以點(diǎn)擊勾選或取消勾選

5.生成動(dòng)態(tài)列表格
模板如下圖所示,具體業(yè)務(wù)需求如下:
1、一個(gè)任務(wù)編號(hào)下綁定的有多個(gè)直屬單位
2、每個(gè)直屬單位下需要匯總供應(yīng)商管理部門(mén)、質(zhì)量部門(mén)、技術(shù)部門(mén)、檢測(cè)部門(mén)、其他相關(guān)意見(jiàn)的審核意見(jiàn)

5.1 list標(biāo)簽
//whyc 是集合(collection)的表達(dá)式,yc是循環(huán)變量的名字,不能是表達(dá)式
<#list whyc as yc>
需要循環(huán)的部分 變量用${(yc.supplierReasonDesc)!}
</#list>首先修改ftl文件,找到我們需要循環(huán)的部門(mén),進(jìn)行添加<#list></#list>標(biāo)簽
此處我們要循環(huán)的是companyName及其其對(duì)應(yīng)的征求部門(mén)的意見(jiàn),我們?cè)趂tl文件找到“備注”這一行結(jié)束的位置,${companyName}這一行開(kāi)始的位置,添加 <#list whyc as yc>
如下圖所示:

注意: list一定要放在要循環(huán)開(kāi)始的位置,即前一行結(jié)束的位置
然后我們需要找到循環(huán)結(jié)束的位置,本案例是到相關(guān)意見(jiàn)的 ${otherReasonDesc}處結(jié)束,找到這一行結(jié)束的位置,添加 </#list> 標(biāo)簽,如圖所示:

添加完<#list></#list>標(biāo)簽后,我們需要調(diào)整變量名,將要?jiǎng)討B(tài)生成的變量改為 循環(huán)變量.變量
本案例中,以供應(yīng)商管理部門(mén)為例,我們需要將 ${supplierOpinion}改為{yc.supplierOpinion}, ${supplierSituationDesc}改為{yc.supplierSituationDesc}, ${supplierReasonDesc}改為{yc.supplierReasonDesc},質(zhì)量部門(mén)、技術(shù)部門(mén)等的變量相同方式修改,如圖所示:

5.2 相關(guān)代碼實(shí)現(xiàn)
public void exportWord(@RequestBody OpinionCollectFeedbackQueryVO query, HttpServletResponse response) throws Exception {
String templateName = "problemSummary.ftl";
String fileName = "problemSummary.docx";
Map<String, Object> dataModel = new HashMap<>();
Map<String, Object> dataModel1 = new HashMap<>();
Map<String, Object> dataModel2 = new HashMap<>();
Map<String, Object> dataModel3 = new HashMap<>();
List<Map<String, Object>> list = new ArrayList<>();
dataModel.put("aptitudeTaskCode","202502211035");
dataModel.put("companyName","com20250221");
dataModel.put("supplierOpinion","供應(yīng)商管理部門(mén)審核意見(jiàn)");
dataModel.put("supplierSituationDesc","供應(yīng)商管理部門(mén)情況描述");
dataModel1.put("companyName","com20250221001");
dataModel1.put("supplierReasonDesc","供應(yīng)商管理部門(mén)原因11111");
dataModel2.put("companyName","com20250221002");
dataModel2.put("supplierReasonDesc","供應(yīng)商管理部門(mén)原因22222");
dataModel3.put("companyName","com20250221003");
dataModel3.put("supplierReasonDesc","供應(yīng)商管理部門(mén)原因33333");
list.add(dataModel1);
list.add(dataModel2);
list.add(dataModel3);
dataModel.put("whyc",list);
// 執(zhí)行導(dǎo)出
FreemarkerUtil.exportWord(templateName, fileName, dataModel,this,response);
}測(cè)試導(dǎo)出結(jié)果如下:

由圖可見(jiàn),我們實(shí)現(xiàn)了動(dòng)態(tài)生成且數(shù)據(jù)和我們測(cè)試類(lèi)里填寫(xiě)的數(shù)據(jù)一致
5.3 合并單元格
要做到合并單元格,我們首先需要了解一下ftl文件一些屬性的含義,我們先看下圖:

單元格屬性設(shè)置:
<w:tcPr>:定義單元格的屬性。w:tcW:設(shè)置單元格寬度為1992單位(dxa類(lèi)型)。w:vMerge:設(shè)置單元格為垂直合并的起始單元格(restart)。w:noWrap:設(shè)置單元格內(nèi)容不換行(0表示不啟用)。w:vAlign:設(shè)置單元格內(nèi)容垂直居中對(duì)齊(center)。

<w:vMerge w:val="continue"/>:設(shè)置單元格為垂直合并的延續(xù)部分(continue),表示該單元格與上方單元格合并。
如果生成的ftl文件里沒(méi)有上述屬性,我們可以 在循環(huán)的部門(mén)的第一行放 <w:vMerge w:val="restart"/> 從第二行開(kāi)始,所有要合并的單元格放<w:vMerge w:val="continue"/>
5.3.1 編輯ftl文件:
start和end的位置,一定要放在循環(huán)開(kāi)始的<w:tcPr>中:


代碼可以這么處理:
public List<Map<String, Object>> checkList(List<Map<String, Object>> list){
String start = "<w:vMerge w:val=\"restart\"/>";
String end = "<w:vMerge w:val=\"continue\"/>";
list.get(0).put("start",start);
for (int i = 1; i < list.size(); i++) {
list.get(i).put("end",end);
}
return list;
}以上便可實(shí)現(xiàn)合并單元格
以上就是Java通過(guò)freemarker生成Word文檔導(dǎo)出的方式講解的詳細(xì)內(nèi)容,更多關(guān)于Java freemarker生成Word導(dǎo)出的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中new與clone操作對(duì)象的比較方法舉例
這篇文章主要給大家介紹了關(guān)于Java中new與clone操作對(duì)象的比較方法,在java中對(duì)象的誕生是我們開(kāi)發(fā)人員new出來(lái)的,對(duì)象的使用也是我們開(kāi)發(fā)人員進(jìn)行操作的,需要的朋友可以參考下2024-07-07
SpringBoot2使用JTA組件實(shí)現(xiàn)基于JdbcTemplate多數(shù)據(jù)源事務(wù)管理(親測(cè)好用)
這篇文章主要介紹了SpringBoot2使用JTA組件實(shí)現(xiàn)基于JdbcTemplate多數(shù)據(jù)源事務(wù)管理(親測(cè)好用),在Spring?Boot?2.x中,整合了這兩個(gè)JTA的實(shí)現(xiàn)分別是Atomikos和Bitronix,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
springboot如何初始化執(zhí)行sql語(yǔ)句
這篇文章主要介紹了springboot初始化執(zhí)行sql語(yǔ)句的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
springboot獲取properties屬性值的多種方式總結(jié)
這篇文章主要介紹了springboot獲取properties屬性值的多種方式總結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Nacos作為配置中心注冊(cè)監(jiān)聽(tīng)器方法
本文主要討論Nacos作為配置中心時(shí),其中配置內(nèi)容發(fā)生更改時(shí),我們的應(yīng)用程序能夠做的事。一般使用監(jiān)聽(tīng)器來(lái)實(shí)現(xiàn)這步操作,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-02-02
Java初學(xué)者常問(wèn)的問(wèn)題(推薦)
本文介紹一些Java初學(xué)者常問(wèn)的問(wèn)題,很多朋友對(duì)可以用%除以一個(gè)小數(shù)嗎? a += b 和 a = a + b 的效果有區(qū)別嗎? 聲明一個(gè)數(shù)組為什么需要花費(fèi)大量時(shí)間? 為什么Java庫(kù)不用隨機(jī)pivot方式的快速排序?等等一系列問(wèn)題有疑惑,下面就通過(guò)本文給大家詳細(xì)介紹下2017-03-03
Spring Cloud OAuth2中/oauth/token的返回內(nèi)容格式
Spring Cloud OAuth2 生成access token的請(qǐng)求/oauth/token的返回內(nèi)容就需要自定義,本文就詳細(xì)介紹一下,感興趣的可以了解一下2021-07-07
springboot+vue項(xiàng)目從第一行代碼到上線部署全流程
本文詳細(xì)介紹了如何從零開(kāi)始搭建一個(gè)基于Spring Boot和Vue.js的前后端分離項(xiàng)目,并涵蓋項(xiàng)目需求分析、技術(shù)選型、項(xiàng)目結(jié)構(gòu)設(shè)計(jì)、前后端交互、部署上線等全過(guò)程,感興趣的朋友跟隨小編一起看看吧2024-11-11

