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

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

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

2.pom文件引入freemarke依賴,安裝依賴
<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工具類
引入freemarker依賴之后,就可以使用Freemarker編寫一個(gè)工具類,專門用于處理文件的導(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 工具類
*/
public class FreemarkerUtil {
/**
* 使用 Freemarker 生成 Word 文件并導(dǎo)出
* @param templateName 模板文件路徑名稱
* @param fileName 生成的文件路徑以及名稱
* @param dataModel 填充的數(shù)據(jù)對象
* @param response HttpServletResponse 對象
*/
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 模板文件路徑名稱
* @param fileName 生成的文件路徑以及名稱
* @param dataModel 填充的數(shù)據(jù)對象
* @param obj 基準(zhǔn)類對象
* @param response HttpServletResponse 對象
*/
private static void generateFile(String templateName, String fileName, Map<String, Object> dataModel, Object obj, HttpServletResponse response) {
try {
// 1、創(chuàng)建配置對象
Configuration config = new Configuration(Configuration.VERSION_2_3_30);
config.setDefaultEncoding("utf-8");
//設(shè)置了模板文件的加載類和路徑,使得Freemarker可以從指定的類路徑下的/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)建寫入器
Writer writer = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8));
// 5、渲染模板文件
template.process(dataModel, writer);
// 6、關(guān)閉寫入器
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}3.3 測試案例代碼
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", "測試機(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");
),則會報(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á)式。${()!} 加括號!號表示可以為null ,也可以解決上述問題,如圖所示:

3.4 解析


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

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

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

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

添加代碼對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", "測試機(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ù)編號下綁定的有多個(gè)直屬單位
2、每個(gè)直屬單位下需要匯總供應(yīng)商管理部門、質(zhì)量部門、技術(shù)部門、檢測部門、其他相關(guā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)的部門,進(jìn)行添加<#list></#list>標(biāo)簽
此處我們要循環(huán)的是companyName及其其對應(yīng)的征求部門的意見,我們在ftl文件找到“備注”這一行結(jié)束的位置,${companyName}這一行開始的位置,添加 <#list whyc as yc>
如下圖所示:

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

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

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)商管理部門審核意見");
dataModel.put("supplierSituationDesc","供應(yīng)商管理部門情況描述");
dataModel1.put("companyName","com20250221001");
dataModel1.put("supplierReasonDesc","供應(yīng)商管理部門原因11111");
dataModel2.put("companyName","com20250221002");
dataModel2.put("supplierReasonDesc","供應(yīng)商管理部門原因22222");
dataModel3.put("companyName","com20250221003");
dataModel3.put("supplierReasonDesc","供應(yīng)商管理部門原因33333");
list.add(dataModel1);
list.add(dataModel2);
list.add(dataModel3);
dataModel.put("whyc",list);
// 執(zhí)行導(dǎo)出
FreemarkerUtil.exportWord(templateName, fileName, dataModel,this,response);
}測試導(dǎo)出結(jié)果如下:

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

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

<w:vMerge w:val="continue"/>:設(shè)置單元格為垂直合并的延續(xù)部分(continue),表示該單元格與上方單元格合并。
如果生成的ftl文件里沒有上述屬性,我們可以 在循環(huán)的部門的第一行放 <w:vMerge w:val="restart"/> 從第二行開始,所有要合并的單元格放<w:vMerge w:val="continue"/>
5.3.1 編輯ftl文件:
start和end的位置,一定要放在循環(huán)開始的<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通過freemarker生成Word文檔導(dǎo)出的方式講解的詳細(xì)內(nèi)容,更多關(guān)于Java freemarker生成Word導(dǎo)出的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot2使用JTA組件實(shí)現(xiàn)基于JdbcTemplate多數(shù)據(jù)源事務(wù)管理(親測好用)
這篇文章主要介紹了SpringBoot2使用JTA組件實(shí)現(xiàn)基于JdbcTemplate多數(shù)據(jù)源事務(wù)管理(親測好用),在Spring?Boot?2.x中,整合了這兩個(gè)JTA的實(shí)現(xiàn)分別是Atomikos和Bitronix,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
springboot獲取properties屬性值的多種方式總結(jié)
這篇文章主要介紹了springboot獲取properties屬性值的多種方式總結(jié),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Spring Cloud OAuth2中/oauth/token的返回內(nèi)容格式
Spring Cloud OAuth2 生成access token的請求/oauth/token的返回內(nèi)容就需要自定義,本文就詳細(xì)介紹一下,感興趣的可以了解一下2021-07-07
springboot+vue項(xiàng)目從第一行代碼到上線部署全流程
本文詳細(xì)介紹了如何從零開始搭建一個(gè)基于Spring Boot和Vue.js的前后端分離項(xiàng)目,并涵蓋項(xiàng)目需求分析、技術(shù)選型、項(xiàng)目結(jié)構(gòu)設(shè)計(jì)、前后端交互、部署上線等全過程,感興趣的朋友跟隨小編一起看看吧2024-11-11

