原创

FreeMarker 指令扩展

温馨提示:
本文最后更新于 2020年08月25日,已超过 1,544 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

什么是 FreeMarker

FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本( HTML 网页,电子邮件,配置文件,源代码等)的通用工具。它不是面向最终用户的,而是一个 Java 类库,是一款程序员可以嵌入他们所开发产品的组件。简单说,
FreeMarker: 模板 + 数据 = 输出

FreeMarker 内建指令 —— include

FreeMarker 中包含很多内建的指令,犹如 JSP 中的标签一样。这里主要讲下 include 指令。参考 Freemarker 官方文档,其语法格式如下:
<#include path> 或 <#include path options>
其中:

  • path: 要包含文件的路径;一个算作是字符串的表达式。(用其他话说, 它不用是一个固定的字符串,它也可以是像 profile.baseDir + "/menu.ftl" 这样的东西。)
  • options: 一个或多个这样的选项: encoding=encoding, parse=parse
    • encoding: 算作是字符串的表达式
    • parse: 算作是布尔值的表达式(为了向下兼容,也接受一部分字符串值)
    • ignore_missing: 算作是布尔值的表达式

你可以使用它在你的模板中插入另外一个 FreeMarker 模板文件 (由 path 参数指定)。 被包含模板的输出格式是在 include 标签出现的位置插入的。被包含的文件和包含它的模板共享变量,就像是被复制粘贴进去的一样。值得注意的是,include 指令不能由被包含文件的内容所替代,它只是当 FreeMarker 每次在模板处理期间到达 include 指令时处理被包含的文件。也就是说,这边可以在执行时,动态指定需包含的 FreeMarker 模板文件。

一个错误场景

假设有这样一个场景,需要介绍全国的每一个省。每个省有一个对应的以省份名称命名的 FreeMarker 模板(当然,这些模板都是在一个总的模板下显示),如:js.ftl。
一般来说,我们可以这样写总的模板,来达到动态包含省份的模板。

<!DOCTYPE html>
<html>
  <head>
      ...
  </head>
  <body>
    <#include "/${province}.ftl"/>
  </body>
</html>

看着上面的代码,是不是觉得 FreeMarker 还是很方便很强大的啊!但是坑来了,假设目前项目还没有开发完成,也就是说,有的省份还没有对应的 FreeMarker 模板,那么会出现什么情况呢?直接报错了。对于项目没做完这种事,我们得找个临时方案啊,给那些没有开发模板的省份来个默认页面,那么用 include 指令貌似有点力不从心。如何解决呢?FreeMarker 有自定义指令的功能,那就定义一个加强版的 include 指令吧,使得该指令在找不到指定模板的情况下,包含一个默认的模板。

public class IncludeXMacro implements TemplateDirectiveModel {

    private static final String PATH_PARAM = "template";

    private static final String DEFALUT_PATH_PARAM = "default_template";

    @Override
    public void execute(Environment environment, @SuppressWarnings("rawtypes") Map params, TemplateModel[] templateModel,
            TemplateDirectiveBody directiveBody) throws TemplateException, IOException {
        TemplateLoader templateLoader = environment.getConfiguration().getTemplateLoader();

        String fullTemplatePath = getFullTemplatePath(environment, params, PATH_PARAM);
        if (templateLoader.findTemplateSource(fullTemplatePath) != null) {
            environment.include(environment.getTemplateForInclusion(fullTemplatePath, null, true));
        } else {
            String defaultFullTemplatePath = getFullTemplatePath(environment, params, DEFALUT_PATH_PARAM);
            if (templateLoader.findTemplateSource(defaultFullTemplatePath) == null) {
                throw new _MiscTemplateException(environment, "Missing template file path:" + defaultFullTemplatePath);
            }
            environment.include(environment.getTemplateForInclusion(defaultFullTemplatePath, null, true));
        }
    }

    /**
     * Description:获取ftl完整路径 <br>
     */
    private String getFullTemplatePath(Environment environment, @SuppressWarnings("rawtypes") Map params, String templatePath)
            throws MalformedTemplateNameException {
        if (!params.containsKey(templatePath)) {
            throw new MalformedTemplateNameException("missing required parameter '" + templatePath, "'");
        }

        String currentTemplateName = environment.getTemplate().getName();
        final String baseName = FilenameUtils.getPath(currentTemplateName);

        final String targetName = params.get(templatePath).toString();
        final String fullTemplatePath = environment.toFullTemplateName(baseName, targetName);

        return fullTemplatePath;
    }
}

FreeMarker 在这边就不详细讲了,官网讲的很清楚。我们给这个自定义的指令起个名字:includeX,然后就可以这样使用了:
<@includeX template="/${province}.ftl" default_template="/default.ftl"/>

正文到此结束
本文目录