86384798 2016-01-17
在web或其他应用中,经常我们需要导出或者预览word文档,比较实际的例子有招聘网站上预览或者导出个人简历,使用POI导出excel会非常的方便,但是如果想导出word,由于其格式控制非常复杂,故而使用POI将会非常麻烦,而FreeMarker则可以较好的解决这个问题;并且,根据FreeMarker的实现原理,预览word也会变得非常简单。
FreeMarker主要有三个部分:模板,数据源以及数据的存储。可想而知,在导出word的时候,我们必须得告诉FreeMarker我们需要导出的word的格式以及将要填充到这个word中的数据,因而模板和数据源是我们需要准备的部分。这里需要另外说明的是,FreeMarker关心的不是模板文件的类型或具体内容,其关心的是模板文件中的ftl标签和其中获取数据的表达式(这部分将在后续进行讲解)。FreeMarker的强大之处也就在这个位置,这里的模板可以是任意类型的模板,而数据源由我们按照指定的格式封装即可。那么也就是说,对于预览操作,我们如果事先制作一个html模板,点击预览后由FreeMarker向按照该模板向新建的html文件中填充数据,接着在前台js中新开窗口将该html文件(填充数据后即为一个静态页面)显示出来即可达到预览的效果。
这里我们以word文件的导出为例来讲解FreeMarker的使用,我们使用的IDE为Intellij IDEA,框架为springboot,项目是使用Maven构建的。
模板文件的创建可以使用word2007及以上版本完成,首先我们创建一个如下格式的word文档:
创建后将该文件以xml格式存储
使用xml文本编辑器打开该xml文件,检查其中的取值表达式是否发生格式错误,如果发生格式错误就将其中错误的部分删除,使其恢复我们填写的格式(格式错误一般会发生在取值表达式中含有特殊字符的时候)。
如图中所示,${user.password}就发生了格式错误,我们将中间错误部分删除后如下:
接着将该模板文件另存为UserList.ftl,文件格式为全部文件:
将该文件复制到项目中,打开并格式化,找到其中${user.username}和${user.password},仔细分析该ftl文件可以发现,在word文档中表格的每一行在xml文件中即为一个<w:tr></w:tr>标签,而在该标签中,每一个<w:tc></w:tc>则对应一个单元格。了解这个之后,我们就要使用ftl语法对该模板文件进行改造。这里我们需要导出的是一个用户列表的文件,每个用户包含一个用户名和密码,那么这里用户所在的这一行(<w:tr></w:tr>)就需要使用ftl中的<#list></#list>标签包含起来:
到此为止,我们的ftl模板就制作完毕了。接下来我们创建后台服务端的代码,实体类创建如下:
public class User { private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public User() { } public User(String username, String password) { this.username = username; this.password = password; } }
Controller层创建如下:
import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/word") public class WordController { @Autowired private WordService wordService; @RequestMapping(value = "/createUserListWord", method = RequestMethod.GET) public ResponseEntity<Void> createUserListWord() { wordService.createUserListWord(); return ResponseEntity.ok().build(); } }Service层的接口及其实现类如下:
public interface WordService { void createUserListWord(); }
import javax.transaction.Transactional; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.stereotype.Service; @Service public class WordServiceImpl implements WordService { public void createUserListWord() { Map<?, ?> root = initData(); //数据源对象 String template = "/template/UserList.ftl"; //模板文件的地址 String path = "E:\\UserList.doc"; //生成的word文档的输出地址 WordUtil.process(root, template, path); } private Map<?, ?> initData() { Map<String, Object> root = new HashMap<String, Object>(); List<User> users = new ArrayList<User>(); User zhangsan = new User("张三", "123"); User lisi = new User("李四", "456"); User wangwu = new User("王五", "789"); users.add(zhangsan); users.add(lisi); users.add(wangwu); root.put("users", users); root.put("title", "用户列表"); return root; } }这里需要说明的一点是,在FreeMarker中,数据一般是以Map,List以及实体类对象的形式存储,这里数据的初始化函数中则将三种形式的数据存储方式都用到了。在模板中取值的时候,对于Map对象中的数据,使用${key}即可获取,这里key表示Map中的键,对于List,则可以使用下标的方式,也可以使用循环的方式,这里我们是将User对象存储于List中,在模板中则可以使用users[i]来获取List中第i个User对象,如users[i].username;也可以使用循环来对List集合进行遍历,如
<#list users as user> ${user.username} </#list>这里users表示存储List的Map的key的值。
最后则是FreeMarker中生成word文档的核心函数:
import freemarker.template.Configuration; import freemarker.template.Template; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; public final class WordUtil { private static Configuration configuration = null; private WordUtil() { throw new AssertionError(); } /** * 根据模板生成相应的文件 * @param root 保存数据的map * @param template 模板文件的地址 * @param path 生成的word文档输出地址 * @return */ public static synchronized File process(Map<?, ?> root, String template, String path) { if (null == root ) { throw new RuntimeException("数据不能为空"); } if (null == template) { throw new RuntimeException("模板文件不能为空"); } if (null == path) { throw new RuntimeException("输出路径不能为空"); } File file = new File(path); String templatePath = template.substring(0, template.lastIndexOf("/")); String templateName = template.substring(template.lastIndexOf("/") + 1, template.length()); if (null == configuration) { configuration = new Configuration(Configuration.VERSION_2_3_23); // 这里Configurantion对象不能有两个,否则多线程访问会报错 configuration.setDefaultEncoding("utf-8"); configuration.setClassicCompatible(true); } configuration.setClassForTemplateLoading(WordUtil.class, templatePath); Template t = null; try { t = configuration.getTemplate(templateName); Writer w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "utf-8")); t.process(root, w); // 这里w是一个输出地址,可以输出到任何位置,如控制台,网页等 w.close(); } catch (Exception e) { throw new RuntimeException(e); } return file; } }至此,使用FreeMarker生成word文档的核心代码已经全部书写完毕,最后打开浏览器访问http://localhost:8081/word/createUserListWord,在E盘根目录下就会生成一个word文档,其内容如下:
是一个替代hibernate的一个作用于数据库的框架。 这里整合后不需要写一些简单的sql语句。 2、在resources下创建templates文件夹,在其下就可以书写页面了,和HTML的风格相似。