( 一 )、 SpringBoot 整合 Freemarker
( 一 )、 SpringBoot 整合 Freemarker
1、简介
官网: http://freemarker.foofun.cn/
这是一个相当老牌的开源的免费的模版引擎。通过 Freemarker 模版,我们可以将数据渲染成 HTML 网页、电子邮件、配置文件以及源代码等。Freemarker 不是面向最终用户的,而是一个 Java 类库,我们可以将之作为一个普通的组件嵌入到我们的产品中。
Freemarker 模版后缀为 .ftl(FreeMarker Template Language)。FTL 是一种简单的、专用的语言,它不是像 Java 那样成熟的编程语言。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。
好了,这是一个简单的介绍,接下来我们来看看 Freemarker 和 Spring Boot 的一个整合操作
2、整合
1、maven依赖
<!--freemarker 模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
3、freemarker 自动配置分析
1、在 org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration
类中,可以看到关于 Freemarker 的自动化配置:
@Configuration
@ConditionalOnClass({ freemarker.template.Configuration.class, FreeMarkerConfigurationFactory.class })
@EnableConfigurationProperties(FreeMarkerProperties.class)
@Import({ FreeMarkerServletWebConfiguration.class, FreeMarkerReactiveWebConfiguration.class, FreeMarkerNonWebConfiguration.class })
public class FreeMarkerAutoConfiguration {
}
从这里可以看出,当 classpath
下存在 freemarker.template.Configuration
以及 FreeMarkerConfigurationFactory
时,配置才会生效,也就是说当我们引入了 Freemarker
之后,配置就会生效。但是这里的自动化配置只做了模板位置检查,其他配置则是在导入的 FreeMarkerServletWebConfiguration
配置中完成的。那么我们再来看看 FreeMarkerServletWebConfiguration
类,部分源码如下:
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass({ Servlet.class, FreeMarkerConfigurer.class })
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
class FreeMarkerServletWebConfiguration extends AbstractFreeMarkerConfiguration {
protected FreeMarkerServletWebConfiguration(FreeMarkerProperties properties) {
super(properties);
}
@Bean
@ConditionalOnMissingBean(FreeMarkerConfig.class)
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
applyProperties(configurer);
return configurer;
}
@Bean
@ConditionalOnMissingBean(name = "freeMarkerViewResolver")
@ConditionalOnProperty(name = "spring.freemarker.enabled", matchIfMissing = true)
public FreeMarkerViewResolver freeMarkerViewResolver() {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
getProperties().applyToMvcViewResolver(resolver);
return resolver;
}
}
我们来简单看下这段源码:
- @ConditionalOnWebApplication 表示当前配置在 web 环境下才会生效。
- ConditionalOnClass 表示当前配置在存在 Servlet 和 FreeMarkerConfigurer 时才会生效。
- @AutoConfigureAfter 表示当前自动化配置在 WebMvcAutoConfiguration 之后完成。
- 代码中,主要提供了 FreeMarkerConfigurer 和 FreeMarkerViewResolver。
- FreeMarkerConfigurer 是 Freemarker 的一些基本配置,例如 templateLoaderPath、defaultEncoding 等
- FreeMarkerViewResolver 则是视图解析器的基本配置,包含了viewClass、suffix、allowRequestOverride、allowSessionOverride 等属性。
另外还有一点,在这个类的构造方法中,注入了 FreeMarkerProperties:
@ConfigurationProperties(prefix = "spring.freemarker")
public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {
public static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/";
public static final String DEFAULT_PREFIX = "";
public static final String DEFAULT_SUFFIX = ".ftlh";
private Map<String, String> settings = new HashMap<>();
/**
* Comma-separated list of template paths.
*/
private String[] templateLoaderPath = new String[] { DEFAULT_TEMPLATE_LOADER_PATH };
/**
* Whether to prefer file system access for template loading to enable hot detection
* of template changes. When a template path is detected as a directory, templates are
* loaded from the directory only and other matching classpath locations will not be
* considered.
*/
private boolean preferFileSystemAccess;
}
FreeMarkerProperties 中则配置了 Freemarker 的基本信息,例如模板位置在 classpath:/templates/
,再例如模板后缀为 .ftlh
,那么这些配置我们以后都可以在 application.properties 中进行修改。
4、自定义其他配置
如果我们要修改模版文件位置等,可以在 application.properties | yml 中进行配置:
# HttpServletRequest的属性是否可以覆盖controller中model的同名项
spring.freemarker.allow-request-override=false
# HttpSession的属性是否可以覆盖controller中model的同名项
spring.freemarker.allow-session-override=false
# 是否开启缓存
spring.freemarker.cache=false
# 模板文件编码
spring.freemarker.charset=UTF-8
# 是否检查模板位置
spring.freemarker.check-template-location=true
# Content-Type的值
spring.freemarker.content-type=text/html
# 是否将HttpServletRequest中的属性添加到Model中
spring.freemarker.expose-request-attributes=false
# 是否将HttpSession中的属性添加到Model中
spring.freemarker.expose-session-attributes=false
# 模板文件后缀
spring.freemarker.suffix=.ftlh
# 模板文件位置
spring.freemarker.template-loader-path=classpath:/templates/
5、使用案例
1、首先我们来创建一个 User 类,如下:
public class User {
private Long id;
private String username;
private String address;
//省略 getter/setter
}
2、再来创建 UserController
:
@Controller
public class UserController {
@GetMapping("/index")
public String index(Model model) {
List<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
User user = new User();
user.setId((long) i);
user.setUsername("dwTest>>>>" + i);
user.setAddress("www.dw.com>>>>" + i);
users.add(user);
}
model.addAttribute("users", users);
return "index";
}
}
3、最后在 freemarker 中渲染数据:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<table border="1">
<tr>
<td>用户编号</td>
<td>用户名称</td>
<td>用户地址</td>
</tr>
<#list users as user>
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.address}</td>
</tr>
</#list>
</table>
</body>
</html>
6、Freemaker 模板解析工具
package com.dw.study.utils;
import com.dw.study.model.User;
import freemarker.cache.StringTemplateLoader;
import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Author dw
* @ClassName FreemarkerUtils
* @Description freemarker 模板解析工具
* @Date 2021/10/27 22:44
* @Version 1.0
*/
public class FreemarkerUtils {
private static final Logger logger = LoggerFactory.getLogger(FreemarkerUtils.class);
/**
* 模板编码格式
*/
private static final String DEFAULT_CHARACTER = "UTF-8";
/**
* freemarker 模板配置
*/
private static Configuration cfg;
static {
// 指定使用的freemarker版本
cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
cfg.setDefaultEncoding(DEFAULT_CHARACTER);
cfg.setTagSyntax(Configuration.AUTO_DETECT_TAG_SYNTAX);
// 取消模板的日志记录.由程序抛出,否则会记录两条
cfg.setLogTemplateExceptions(false);
cfg.setTemplateExceptionHandler(new MyTemplateExceptionHandler());
}
/**
* 对模板进行渲染
*
* @param data 数据Map
* @param tplStr 模板
* @return
*/
public static String generateString(Map<String, Object> data, String tplStr) {
String result = null;
String name = "myStrTpl";
try {
StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
stringTemplateLoader.putTemplate(name, tplStr);
cfg.setTemplateLoader(stringTemplateLoader);
Template template = cfg.getTemplate(name);
StringWriter out = new StringWriter();
template.process(data, out);
out.flush();
result = out.toString();
out.close();
} catch (Exception e) {
e.printStackTrace();
logger.error("模板解析失败!!! {}", e.getMessage());
}
return result;
}
/**
* 将模板渲染以后保存到文件
*
* @param templateFileDir 模板所在目录
* @param fileName 模板文件的名称
* @param targetFilePath 渲染后保存文件路径/名称
* @param dataMap 数据
* @return
*/
public static boolean renderingTemplateFile(String templateFileDir,
String fileName,
String targetFilePath,
Map<String, Object> dataMap) {
boolean flag = true;
try {
// 设置文件所在目录的路径
cfg.setClassForTemplateLoading(FreemarkerUtils.class, templateFileDir);
// 获取模版
Template template = cfg.getTemplate(fileName);
// 设置输出文件名,和保存路径
File outFile = new File(targetFilePath);
// 将模板和数据模型合并生成文件 重点设置编码集
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "UTF-8"));
// 生成文件
template.process(dataMap, out);
// 关闭流
out.flush();
out.close();
} catch (Exception e) {
logger.error("生产模板文件失败!", e);
flag = false;
}
return flag;
}
/***
* 解析模板时存在错误, 继续解析, 不抛出异常
*/
private static class MyTemplateExceptionHandler implements TemplateExceptionHandler {
@Override
public void handleTemplateException(TemplateException te, Environment env, java.io.Writer out) throws TemplateException {
// logger.error("模板解析时存在错误:{}", te.getMessage());
try {
// 渲染错误到模板文件中
out.write("[ERROR: " + te.getMessage() + "]");
} catch (IOException e) {
// throw new TemplateException("Failed to print error message. Cause: " + e, env);
logger.error("模板解析时存在错误:{}", e.getMessage());
}
}
}
public static void main(String[] args) {
Map<String, Object> map = new HashMap<>();
// ##############【测试渲染字符串】#########################################
// map.put("userName", "zhengxl5566");
// map.put("caseNo", "AJ00000001");
// map.put("descrip", "这是描述信息==========");
//// String template="案件编号为:${caseNo!} "
//// + " 日期为:${date!} "
//// + " 自动获取日期为:${ .now?string('yyyy年MM月dd日')}"
//// + "描述:${descrip!}";
// String template = "<p>你好,\n" +
// " <#if userName == \"Java 课代表\">\n" +
// " 111<strong>${userName}</strong>\n" +
// " <#elseif userName == \"zhengxl5566\">\n" +
// " 222${userName}\n" +
// " <#else>\n" +
// " 333${userName}\n" +
// " </#if>\n" +
// "</p>";
// String generateString = FreemarkerUtils.generateString(map, template);
// System.out.println("------");
// System.out.println(generateString);
// ##############【测试渲染ftl文件, 并输出】#########################################
List<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
User user = new User();
user.setAge(i);
user.setUsername("dwTest>>>>" + i);
user.setAddress("www.dw.com>>>>" + i);
users.add(user);
}
map.put("users", users);
FreemarkerUtils.renderingTemplateFile("/template", "test.ftl", "G:\\test.ftl", map);
}
}
7、Freemaker自定义指令、函数
参考gitee源码: https://gitee.com/my-study-project/springboot-freemarker