应邀写的一篇文章:Java项目中利用Freemarker模板引擎导出--生成Word文档
在项目中难免和各种数据报表打交道,如导出XX申请表,登记表,推荐表之类。就可以通过现有信息导出Word文档。基于Java语言来导出Word文档的方式也有很多种,如Jacob,Apache POI,Freemarker,PageOffice,java2word 等等。。。。
在这里将通过Freemarker这个模板引擎来实现导出 Word,项目不限于Swing,SSH,SSM,Spring Boot 之类的。。。。。。。。。。
首先说一下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 提供的
>
>org.springframework.boot >
>spring-boot-starter-freemarker >
>
# 也可以选择其他版本如:
>
>org.freemarker >
>freemarker >
>2.3.30 >
>
# 网址:
https://mvnrepository.com/artifact/org.freemarker/freemarker
项目搭建好之后,开始制作FTL模板,这次选择一个XXX推荐表来实现:
如Word文档:
开始制作模板:
首先,我们需要在模板上面添加占位符,${XXXXX}。这里全用了拼音简写
添加好占位符之后,将该Word文档另存为XML格式的文件
修改一下文件名称,保存即得到一个XML文件
打开观察一下,正常
接下来,利用文本编辑器打开这个文件,如Notepad++、Sublime Text、EditPlus、UltraEdit什么都行,我这里用VSCode。如图
直接搜索前面添加的占位符${xxxx},这里可能会出现一些问题,如符号和花括号分离,这时候就得对它进行修改,恢复原样
修改后
等检查一遍没问题之后呢,就修改完成了,开始进入下一步修改文件后缀
接下来开始代码编写部分,一般来说这里都是用数据库的数据作为导出数据。但由于麻烦就不写数据库部分了,在这里直接用提交表单的数据来进行导出。
发现院系YX和邮箱YX重复了,修改邮箱为DZYX
首先将实体类封装为导出数据所需的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));
}
编辑好格式的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测试页:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试title>
head>
<body>
<h2>测试h2>
<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>
body>
html>
freemarker 各个版本的jar包:Maven仓库