全面探索 FreeMarker 模版引擎的扩展性

2023-10-27

FreeMarker 模版引擎简介

FreeMarker 是一个采用 Java 开发的模版引擎,是一个基于模版生成文本的通用工具。 FreeMarker 被设计用来生成 HTML Web 页面,特别是基于 MVC 模式的应用程序。虽然 FreeMarker 具有一些编程的能力,但通常由 Java 程序准备要显示的数据,由 FreeMarker 生成页面,并通过模板显示准备的数据(如下图)。

图 1. FreeMarker 工作原理

FreeMarker 工作原理

FreeMarker 非常简单,只需要一个 Freemarker.jar 文件(无需任何配置文件)即可包含所有的功能。但 FreeMarker 的功能却是非常的强大,相比较另外一个非常著名的 Java 模版引擎 —— Velocity 来说,FreeMarker 的功能让您惊叹,但其学习的曲线也较 Velocity 要长很多。

本文主要介绍如何利用 FreeMarker 强大的可扩展性来输出各种文本信息,这不是 FreeMarker 的入门学习材料,如果您尚未对 FreeMarker 有所了解,或者还没有使用过 FreeMarker 的话,那不妨先上手后再来阅读本文。

FreeMarker 主要提供了如下几个方面的扩展性功能:

  1. 自定义宏
  2. 自定义函数
  3. 自定义模版文件加载器
  4. 缓存处理
  5. 异常处理

FreeMarker 自定义宏

FreeMarker 和 Velocity 都提供可自定义宏的功能,但 FreeMarker 的宏功能更加强大,包括允许通过名称和参数的位置进行参数传递;允许设置参数的默认值;支持宏的嵌套;宏可以先使用再声明;支持命名空间等。

下面我们针对这些功能给出一个简单但是完整的演示例子,先看看代码:

清单 1. 宏定义文件 ( html.ftl )

1

2

3

4

5

6

7

8

9

10

11

12

<#macro html title charset="utf-8" lang="zh-CN">

 <html>

 <head>

  <meta http-equiv="Content-Type" content="text/html; charset=${charset}" />

  <meta http-equiv="Content-Language" content="${lang}"/>

  <title>${title}</title>

 </head>

 <body>

    <#nested>

 </body>

 </html>

 </#macro>

在这个宏定义文件中,我们声明了一个名为 html 的宏,该宏是为了生成一个 HTML 页面的框架。它具有三个参数分别是 title 、charset 和 lang ,其中 charset 和 lang 分别指定了默认的值。

再来看看如何调用该宏:

清单 2. 调用宏

1

2

3

4

<#include "html.ftl">

 <@html title="FreeMarker 宏测试 ">

    欢迎使用 FreeMarker 模版引擎

 </@html>

在 FreeMarker 中,用户自定义的宏必须以 @ 开头来调用,并传入页面标题 title 的参数。而 <@html> 标签中包含的文本“欢迎使用 FreeMarker 模版引擎”将替换宏定义中的 <#nested> 标签。因此这个模版将会生成如下的 HTML 信息:

清单 3. 模版生成结果

1

2

3

4

5

6

7

8

9

10

<html>

 <head>

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

  <meta http-equiv="Content-Language" content="zh-CN"/>

  <title>FreeMarker 宏测试 </title>

 </head>

 <body>

    欢迎使用 FreeMarker 模版引擎

 </body>

 </html>

而 Velocity 本身并不提供嵌套模版的功能,它必须依赖 Velocity-Tools 这个项目来实现。另外对于一些需要实现更复杂逻辑的宏,还可以通过 Java 类来进行定义。 FreeMarker 提供了一个 TemplateDirectiveModel 接口,通过实现该接口可以实现自定义宏的功能,这样可以更好的跟应用逻辑进行集成,不过需要注意的是暂不支持通过参数的位置来调用宏,调用时必须指定参数名,该问题将在 FreeMarker 2.4 中得以解决。下面是一个简单的例子:

清单 4. 自定义宏功能的例子

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

/**

 * 将标签中的代码全部转为大写并输出

 * @author Winter Lau (javayou@gmail.com)

 * 使用方法:

 * <@upper>Welcome to http://www.oschina.net</@upper>

 */

public class UpperDirective implements TemplateDirectiveModel {

     

    public void execute(Environment env,

            Map params, TemplateModel[] loopVars,

            TemplateDirectiveBody body)

            throws TemplateException, IOException {

        // Check if no parameters were given:

        if (!params.isEmpty()) {

            throw new TemplateModelException(

                    "This directive doesn't allow parameters.");

        }

        if (loopVars.length != 0) {

                throw new TemplateModelException(

                    "This directive doesn't allow loop variables.");

        }

         

        // If there is non-empty nested content:

        if (body != null) {

            // Executes the nested body. Same as <#nested> in FTL, except

            // that we use our own writer instead of the current output writer.

            body.render(new UpperCaseFilterWriter(env.getOut()));

        } else {

            throw new RuntimeException("missing body");

        }

    }

     

    /**

     * A {@link Writer} that transforms the character stream to upper case

     * and forwards it to another {@link Writer}.

     */

    private static class UpperCaseFilterWriter extends Writer {

        

        private final Writer out;

            

        UpperCaseFilterWriter (Writer out) {

            this.out = out;

        }

 

        public void write(char[] cbuf, int off, int len)

                throws IOException {

            char[] transformedCbuf = new char[len];

            for (int i = 0; i < len; i++) {

                transformedCbuf[i] = Character.toUpperCase(cbuf[i + off]);

            }

            out.write(transformedCbuf);

        }

 

        public void flush() throws IOException {

            out.flush();

        }

 

        public void close() throws IOException {

            out.close();

        }

    }

 

}

接下来我们需要重载 FreemarkerServlet ,植入该指令扩展,代码如下:

清单 5. 重载 FreemarkerServlet

1

2

3

4

5

6

@Override

protected Configuration createConfiguration() {

    Configuration cfg = super.createConfiguration();

    cfg.setSharedVariable("upper", new UpperDirective());

    return cfg;

}

在页面模版中使用<@upper>Welcome to http://www.oschina.net</@upper>试试吧。

FreeMarker 自定义函数

与宏不同,宏一般用来执行某个过程,而函数可以定义返回值,例如对一组数据求和、平均值、最大值、最小值等等运算。 FreeMarker 的函数支持可变个数的参数。例如下面定义了一个求平均值的函数:

清单 6. 求平均值的函数例子

1

2

3

4

5

6

7

8

9

<#function avg nums...>

  <#local sum = 0>

  <#list nums as num>

    <#local sum = sum + num>

  </#list>

  <#if nums?size != 0>

    <#return sum / nums?size>

  </#if>

 </#function>

其中函数名为 avg ,支持可变个数的参数 nums 。可用下面的代码来要调用该函数:

1

${avg(3,5,100,3453)}

跟宏相同,FreeMarker 也可以用 Java 来编写自定义函数。例如我们用 Java 代码来生成一个随机的整数,其代码如下:

清单 7. 使用 Java 编写的自定义函数

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

/**

 * 生成一个随机的整数

 * @author Winter Lau (javayou@gmail.com)

 * @url http://www.oschina.net

 */

public class RandomFunction implements TemplateMethodModel {

 

    final static Random rnd_seed = new Random(System.currentTimeMillis());

     

    /* (non-Javadoc)

     * @see freemarker.template.TemplateMethodModel#exec(java.util.List)

     */

    @SuppressWarnings("unchecked")

    public Object exec(List args) throws TemplateModelException {

        return rnd_seed.nextInt(Integer.parseInt((String)args.get(0)));

    }

 

}

同样的,需要将该函数的定义植入 FreeMarker :

1

cfg.setSharedVariable("rand",newRandomFunction());

使用方法:${rand(1000)} 。

FreeMarker 自定义模版文件加载器

模版文件加载器用来告诉 FreeMarker 引擎到什么地方去加载模版文件。 FreeMarker 自带了三种文件加载器,分别是:文件目录加载器、类路径加载器以及 Web 上下文加载器。当在 Web 环境中使用 FreemarkerServlet 来加载模版文件时,默认使用第三种加载器,并通过 Servlet 的配置 TemplatePath 来指定模版文件所存放的路径,该路径是相对于 Web 的根目录的。

在某种情况下,我们可能会希望把模版文件的源码进行加密处理,例如我们使用 DES 加密方式将模版源文件加密后进行存储,然后我们通过自行实现一个加密的模版文件加载器来读取这些模版文件,解密后交给 FreeMarker 引擎解释执行并得到执行的结果。 FreeMarker 为模版文件加载器定义了一个统一的接口 —— TemplateLoader ,该接口有以下四个方法:

closeTemplateSource 关闭模版资源
findTemplateSource 根据名称返回指定的模版资源
getLastModified 返回模版资源最后一次修改的时间
getReader 返回读取模版资源的 Reader

为了简单起见,我们可以在 FreeMarker 自带的加载器上进行扩展,重写 getReader 方法对读取到的模版文件内容进行解密后生成一个新的 Reader 实例并返回(详细过程不再叙述)。

FreeMarker 自带的几个 TemplateLoader 分别是:

  1. ClassTemplateLoader :基于类路径的模版加载器
  2. FileTemplateLoader :基于文件目录的模版加载器
  3. MultiTemplateLoader :多种加载器的混合
  4. StringTemplateLoader :基于字符串的模版加载器
  5. URLTemplateLoader :基于 URL 的模版加载器
  6. WebappTemplateLoader :基于 Web 上下文的模版加载器

重载模版加载器后通过下面代码使之生效:

1

cfg.setTemplateLoader(loader)

FreeMarker 缓存处理

FreeMarker 的缓存处理主要用于模版文件的缓存,一般来讲,模版文件改动不会很频繁,在一个流量非常大的网站中,如果频繁的读取模版文件对系统的负担还是很重的,因此 FreeMarker 通过将模版文件的内容进行缓存,来降低模版文件读取的频次,降低系统的负载。

当处理某个模版时,FreeMarker 直接从缓存中返回对应的 Template 对象,并有一个默认的机制来保证该模版对象是跟模版文件同步的。如果使用的时候 FreemarkerServlet 时,有一个配置项 template_update_delay 用来指定更新模版文件的间隔时间,相当于多长时间检测一下是否有必要重新加载模版文件,0 表示每次都重新加载,否则为多少毫秒钟检测一下模版是否更改。

FreeMarker 定义了一个统一的缓存处理接口 CacheStorage ,默认的实现是 MruCacheStorage 最近最少使用的缓存策略。一般情况下,很少需要对缓存进行扩展处理。您可以通过下面的代码指定最大缓存的模版数:

1

cfg.setCacheStorage(new freemarker.cache.MruCacheStorage(20, 250))

其中第一个参数是最大的强引用对象数,第二个为最大的弱引用对象数。这两个值 FreeMarker 默认的是 0 和 Integer.MAX_VALUE,表明模版缓存数是无限的。

FreeMarker 异常处理

当使用 FreeMarker 做为模版引擎的时候,可能发生的异常包括:

配置异常:配置异常指的是 FreeMarker 初始化时发生的异常,例如错误的配置导致,该异常时由 FreeMarker 的 API 抛出来的。

模版加载异常:模版加载异常可能是模版不存在或者没有读权限,或者是解析模版时发生错误,例如模版语法错误等。

模版执行异常:模版执行异常是指模版已经成功的加载但在执行过程中由于代码执行错误所抛出的异常,这类异常一般都是用户的代码导致。

正常情况下,前两种异常会在开发过程中就会发现并得以解决,而第三种异常往往跟实际的运行环境和数据有关,例如由于某些数据不存在导致的空指针异常等等。因此第三种异常才是我们真正需要关心以及监控的。

为此,FreeMarker 定义了一个统一的异常处理接口 TemplateExceptionHandler 。该接口只有一个方法如下:

1

2

3

void handleTemplateException(TemplateException te,

    Environment env,

    java.io.Writer out)

通过调用 cfg.setTemplateExceptionHandler 来使用自定义的异常处理方法。下面是一个简单的异常处理扩展的例子:

清单 8. 异常处理扩展的例子

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

class MyTemplateExceptionHandler implements TemplateExceptionHandler {

     

    public void handleTemplateException(TemplateException te,

        Environment env, java.io.Writer out)

        throws TemplateException {

        try {

            out.write("[ERROR: " + te.getMessage() + "]");

        } catch (IOException e) {

            throw new TemplateException(

                "Failed to print error message. Cause: " + e, env);

        }

    }

     

}

 

...

 

cfg.setTemplateExceptionHandler(new MyTemplateExceptionHandler());

Eclipse 的 FreeMarker 插件

为了方便编写 FreeMarker 模版,您可以使用 FreeMarker IDE 这个 Eclipse 插件。该插件具有语法高亮、错误提示等功能。虽然该插件还有很多问题,而且已经很久没更新了,但也能很好地使用。

总结

从上面对于 FreeMarker 的可扩展性的介绍来看,FreeMarker 确实是一个功能非常之强大的模版引擎,可以说远在 Velocity 之上。不过从使用的直观程度以及上手的时间来看,其复杂度也大大的超过了 Velocity 。当我们在面临这两个模版引擎的选择时,不能只是从功能或者容易上手的角度来决定,更应该根据业务本身的需要综合进行比较。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

全面探索 FreeMarker 模版引擎的扩展性 的相关文章

随机推荐

  • nginx代理常见问题

    1 http200 但是返回We re sorry but XXXX doesn t work properly without JavaScript enabled Please enable it to continue 项目1 可能原
  • React.js 之筛选篇

    span style font size 14px 这个框架有听过好几次了 但自己没实现过 今天终于自己学了下 模仿写了这个过滤 妙味视频里面的 目前遇到的情况是用babel会丢失代码提示 但在浏览器中可视 划分组件 组件链接 span
  • 交叉熵损失函数优缺点_如何简单通俗的理解交叉熵损失函数?

    前面小编给大家简单介绍过损失函数 今天给大家继续分享交叉熵损失函数 直接来看干货吧 一 交叉熵损失函数概念 交叉熵损失函数CrossEntropy Loss 是分类问题中经常使用的一种损失函数 公式为 接下来了解一下交叉熵 交叉熵Cross
  • 多线程任务Rollback

    问题 多线程任务 一个任务执行错误 其他任务应该同步取消 1 主线程监视 主线程监视任务线程 当一个任务线程出现执行错误时 直接调用System exit 0 结束程序 任务线程代码 package com example thread c
  • 小程序服务器角色,小程序在我们的生活中扮演什么角色?

    原标题 小程序在我们的生活中扮演什么角色 我们给大家讲过关于小程序的相关问题 还有互联网 的相关问题 相信大家还不知道这两者之间的关系 今天我们给大家讲解一下关于小程序与互联网 的关联 我们再来回忆以下关于小程序的概念 对于用户来说 小程序
  • 应用角度看kafka的术语和功能

    kafka的术语 Terminology Topic 和Consumer Group Topic 每条发布到 Kafka 集群的消息都有一个类别 这个类别被称为 Topic 物理上不同 Topic 的消息分开存储 逻辑上一个 Topic 的
  • 入门必备小游戏之炸金花

    游戏的规则 一付扑克牌 去掉大小王 每个玩家发3张牌 最后比大小 看谁赢 牌型 豹子 三张一样的牌 如3张6 分值100 顺金 又称同花顺 即3张同样花色的顺子 如红桃 5 6 7 分值75 顺子 又称拖拉机 花色不同 但是顺子 如红桃5
  • 差分+差分矩阵(更适合新手宝宝体质)

    快速掌握差分以及差分矩阵 文章目录 快速掌握差分以及差分矩阵 前言 差分 差分的定义 官方解释 差分自定义 跟前缀和放在一起理解 差分数组的应用 题目描述 差分矩阵 与前缀和矩阵进行比较 差分矩阵定义 官方解释 自定义 修改操作 跟前缀和对
  • openGauss学习笔记-43 openGauss 高级数据管理-事件触发器

    文章目录 openGauss学习笔记 43 openGauss 高级数据管理 事件触发器 43 1 语法格式 43 2 参数说明 43 3 示例 openGauss学习笔记 43 openGauss 高级数据管理 事件触发器 触发器会在指定
  • 物联网毕业设计 基于STM32的环境质量监测系统(源码+原理图+论文)

    文章目录 0 前言 1 设计架构 功能设计 2 原理图 3 软件设计 4 实现效果 5 相关代码 6 最后 0 前言 这两年开始毕业设计和毕业答辩的要求和难度不断提升 传统的毕设题目缺少创新和亮点 往往达不到毕业答辩的要求 这两年不断有学弟
  • 傻白入门芯片设计,三大基本定律(十)

    1 摩尔定律 Moore s Law 集成电路上可以容纳的晶体管数目在大约每经过18个月到24个月便会增加一倍 换言之 处理器的性能大约每两年翻一倍 同时价格下降为之前的一半 2 登纳德缩放定律 Dennard Scaling 随着晶体管尺
  • 2023.3.17

    Goby预置了最具攻击效果的漏洞引擎 覆盖Weblogic Tomcat等最严重漏洞 每天从互联网 如CVE 会产生大量的漏洞信息 我们筛选了会被用于真实攻击的漏洞进行每日更新 Goby也提供了可以自定义的漏洞检查框架 发动了互联网的大量安
  • go数组转tree

    数组转 tree目前发现就二种方式 go实现了两种 1 递归模式 2 双重循环 初始化数据 List 结构体 type List struct Name string json name Id int json id Pid int jso
  • (Struts2学习篇) Struts2 拦截器

    版本 struts2 5 5 此实例实现功能 用户需要指定用户名登陆 登陆成功进入相应页面执行操作 否则返回到登陆页面进行登陆 当直接访问操作页面 登陆后才能访问的页面 时则不允许 须返回登陆页面 1 Struts xml配置文件
  • [Ubuntu]安装微信/QQ/TIM的简便方法

    之前尝试过很多在Ubuntu上安装微信 QQ TIM等软件的方法 大多数都是使用Deepin wine再加 deb安装包来完成的 虽然也可以用 但是有时会有各种兼容问题出现 以前没有计较过 都是一个个解决 凑活着用 现在 偶然间找到一个可以
  • SOLO算法学习

    SOLO神经网络学习 在博客的最开始 先简单谈谈图像处理的几大目标 首先是最基本的目标分类 Object Classification 输出结果 图像中是气球 然后目标检测 Object detection 是在图像分类的基础上 给出每个气
  • 【转载】Chrome插件(扩展应用)开发全攻略

    背景 遇到了一些大量重复操作的任务 需要从页面上获取信息 查询对应的说明 整理出对应内容 然后录入到页面 提交保存 操作过程很繁琐 内容需要人为去做细微的修改 无法完全模板化 所以不好批量处理 鉴于此 考虑使用chrome插件 简化其中一些
  • VC++ CString Find函数的用法说明

    名称 CString Find 在一个较大的 字符 串中查找字符或子字符串 int Find TCHAR ch const int Find LPCTSTR lpszSub const int Find TCHAR ch int nStar
  • pycharm入门

    pycharm基础使用步骤 1 下载pycharm 2 新建Python工程 1 如下 点击Create New Project 2 选择保存位置 点击create 3 命名 打开界面如下 4 这时就可以创建文件了 项目文件夹右击new g
  • 全面探索 FreeMarker 模版引擎的扩展性

    FreeMarker 模版引擎简介 FreeMarker 是一个采用 Java 开发的模版引擎 是一个基于模版生成文本的通用工具 FreeMarker 被设计用来生成 HTML Web 页面 特别是基于 MVC 模式的应用程序 虽然 Fre