20200426-27 关于Freemarker中的入门,数据模型,配置,其他 知识梳理

官方链接:http://freemarker.foofun.cn/dgui_template.html

目录

1.入门

1.1创建 Configuration 实例

1.2 创建数据模型

1.3 获取模板

1.4 合并模板和数据模型

1.5 将代码放在一起

2.数据模型

2.1 基本内容

2.2 标量

"日期" 类型的难点

2.3 容器(重要)

2.3.1 哈希表

2.3.2 序列

2.3.3 集合

2.4 方法

2.5 指令

示例 1

示例 2

注意

2.6 结点变量

2.7 对象包装

2.7.1 默认对象包装器

object_wrapper Configuration 的默认设置是 freemarker.template.DefaultObjectWrapper 实例。除非有特别的需求,那么建议使用这个对象包装器,或者是自定义的 DefaultObjectWrapper 的子类。

2.7.2 自定义对象包装示例

3.配置

3.1 基本内容

3.2 共享变量

3.3 配置设置

3.4 模板加载

3.4.1 模板加载器-内建模板加载器

3.4.2 模板加载器-从多个位置加载模板

3.4.3 模板加载器-从其他资源加载模板

3.4.4 模板加载器-模板名称(模板路径)

3.4.5 模板缓存

3.5 错误控制

3.5.1 可能的异常

3.5.2 根据TemplateException来自定义处理方式

3.5.3 在模板中明确地处理错误

3.6 "不兼容改进"设置

3.6.1 它要做什么

3.6.2 如何设置

4.其他

4.1 变量,范围

4.2 字符集问题

4.3 多线程

4.4 Bean的包装

4.4.1 模板哈希表模型功能(TemplateHashModel functionality)

4.4.2 说一点安全性

4.4.3 模板标量模型功能(TemplateScalarModel functionality)

4.4.4 模板数字模型功能(TemplateNumberModel functionality)

4.4.5 模板集合模型功能(TemplateCollectionModel functionality)

4.4.6 模板序列模型功能(TemplateSequenceModel functionality)

4.4.7 模板方法模型功能(TemplateMethodModel functionality)

4.4.8 解包规则

4.4.9 访问静态方法

4.4.10 访问枚举类型

4.5 日志

4.5.1 日志库选择

4.5.2 日志分类

4.6 在Servlet中使用FreeMarker

4.6.1 在"Model 2"中使用FreeMarker(重要)

4.6.2 包含其它Web应用程序资源中的内容 (重要)

4.6.3 在FTL中使用自定义JSP标签

4.6.4 在JSP页面中嵌入FTL

4.7 为FreeMarker配置安全策略

4.8 遗留的XML包装实现

4.8.1 模板标量模型(TemplateScalarModel)

4.8.2  模板集合模型(TemplateCollectionModel)

4.8.3  模板序列模型(TemplateSequenceModel)

4.8.4 模板哈希表模型(TemplateHashModel)

4.8.5 模板方法模型(TemplateMethodModel)

4.8.6 命名空间处理

4.9 和Ant一起使用FreeMarker

4.10 Jython包装器


1.入门

1.1创建 Configuration 实例

首先,你应该创建一个 freemarker.template.Configuration 实例, 然后调整它的设置。Configuration 实例是存储 FreeMarker 应用级设置的核心部分。同时,它也处理创建和 缓存 预解析模板(比如 Template 对象)的工作。

也许你只在应用(可能是servlet)生命周期的开始执行一次

从现在开始,应该使用  实例配置(也就是说,它是单例的)。 请注意不管一个系统有多少独立的组件来使用 FreeMarker, 它们都会使用他们自己私有的 Configuration 实例。

当使用多线程应用程序(比如Web网站),Configuration 实例中的设置就不能被修改。它们可以被视作为 "有效的不可改变的" 对象, 也可以继续使用 安全发布 技术 (参考 JSR 133 和相关的文献)来保证实例对其它线程也可用。比如, 通过final或volatile字段来声明实例,或者通过线程安全的IoC容器,但不能作为普通字段。 (Configuration 中不处理修改设置的方法是线程安全的。)

1.2 创建数据模型

在简单的示例中你可以使用 java.lang 和 java.util 包中的类, 还有用户自定义的Java Bean来构建数据对象:

  • 使用 java.lang.String 来构建字符串。

  • 使用 java.lang.Number 来派生数字类型。

  • 使用 java.lang.Boolean 来构建布尔值。

  • 使用 java.util.List 或Java数组来构建序列。

  • 使用 java.util.Map 来构建哈希表。

  • 使用自定义的bean类来构建哈希表,bean中的项和bean的属性对应。比如, product 的 price 属性 (getProperty())可以通过 product.price 获取。(bean的action也可以通过这种方式拿到; 要了解更多可以参看 这里)

我们为 模板开发指南部分演示的第一个例子 来构建数据模型。为了方便说明,这里再展示一次示例:

在真实应用系统中,通常会使用应用程序指定的类来代替 Map, 它会有JavaBean规范规定的 getXxx/isXxx 方法。比如有一个和下面类似的类:

如果配置设置项 object_wrapper 的值是用于所有真实步骤, 这里描述的行为才好用。任何由 ObjectWrapper 包装成的哈希表 可以用作根root,也可以在模板中和点、 [] 操作符使用。 如果不是包装成哈希表的对象不能作为根root,也不能像那样在模板中使用。

1.3 获取模板

模板代表了 freemarker.template.Template 实例。典型的做法是从 Configuration 实例中获取一个 Template 实例。无论什么时候你需要一个模板实例, 都可以使用它的 getTemplate 方法来获取。在 之前 设置的目录中的 test.ftl 文件中存储 示例模板,那么就可以这样来做:

Template temp = cfg.getTemplate("test.ftl");

当调用这个方法的时候,将会创建一个 test.ftl 的 Template 实例,通过读取 /where/you/store/templates/test.ftl 文件,之后解析(编译)它。Template 实例以解析后的形式存储模板, 而不是以源文件的文本形式。

Configuration 缓存 Template 实例,当再次获得 test.ftl 的时候,它可能再读取和解析模板文件了, 而只是返回第一次的 Template 实例。

1.4 合并模板和数据模型

我们已经知道,数据模型+模板=输出,我们有了一个数据模型 (root) 和一个模板 (temp), 为了得到输出就需要合并它们。这是由模板的 process 方法完成的。它用数据模型root和 Writer 对象作为参数,然后向 Writer 对象写入产生的内容。 为简单起见,这里我们只做标准的输出:

Writer out = new OutputStreamWriter(System.out);

temp.process(root, out)

这会向你的终端输出你在模板开发指南部分的 第一个示例 中看到的内容。

Java I/O 相关注意事项:基于 out 对象,必须保证 out.close() 最后被调用。当 out 对象被打开并将模板的输出写入文件时,这是很电影的做法。其它时候, 比如典型的Web应用程序,那就 不能 关闭 out 对象。FreeMarker 会在模板执行成功后 (也可以在 Configuration 中禁用) 调用 out.flush(),所以不必为此担心。

请注意,一旦获得了 Template 实例, 就能将它和不同的数据模型进行不限次数 (Template实例是无状态的)的合并。此外, 当 Template 实例创建之后 test.ftl 文件才能访问,而不是在调用处理方法时。

1.5 将代码放在一起

这是一个由之前的代码片段组合在一起的源程序文件。 千万不要忘了将 freemarker.jar 放到 CLASSPATH 中。

2.数据模型

2.1 基本内容

在 入门 章节中, 我们已经知道如何使用基本的Java类(MapString,等)来构建数据模型了。在内部,模板中可用的变量都是实现了 freemarker.template.TemplateModel 接口的Java对象。 但在数据模型中,可以使用基本的Java集合类作为变量,因为这些变量会在内部被替换为适当的 TemplateModel 类型。这种功能特性被称作是 对象包装。对象包装功能可以透明地把 任何 类型的对象转换为实现了 TemplateModel 接口类型的实例。这就使得下面的转换成为可能,如在模板中把 java.sql.ResultSet 转换为序列变量, 把 javax.servlet.ServletRequest 对象转换成包含请求属性的哈希表变量, 甚至可以遍历XML文档作为FTL变量(参考这里)。包装(转换)这些对象, 需要使用合适的,也就是所谓的 对象包装器 实现(可能是自定义的实现); 这将在后面讨论。 现在的要点是想从模板访问任何对象,它们早晚都要转换为实现了 TemplateModel 接口的对象。那么首先你应该熟悉来写 TemplateModel 接口的实现类。

有一个粗略的 freemarker.template.TemplateModel 子接口对应每种基本变量类型(TemplateHashModel 对应哈希表, TemplateSequenceModel 对应序列, TemplateNumberModel 对应数字等等)。比如,想为模板使用 java.sql.ResultSet 变量作为一个序列,那么就需要编写一个 TemplateSequenceModel 的实现类,这个类要能够读取 java.sql.ResultSet 中的内容。我们常这么说,使用 TemplateModel 的实现类 包装 了 java.sql.ResultSet,基本上只是封装 java.sql.ResultSet 来提供使用普通的 TemplateSequenceModel 接口访问它。请注意一个类可以实现多个 TemplateModel 接口;这就是为什么FTL变量可以有多种类型 (参看 模板开发指南/数值,类型/基本内容)

请注意,这些接口的一个细小的实现是和 freemarker.template 包一起提供的。 例如,将一个 String 转换成FTL的字符串变量, 可以使用 SimpleScalar,将 java.util.Map 转换成FTL的哈希表变量,可以使用 SimpleHash 等等

如果想尝试自己的 TemplateModel 实现, 一个简单的方式是创建它的实例,然后将这个实例放入数据模型中 (也就是把它 放到 哈希表的根root上)。 对象包装器将会给模板提供它的原状,因为它已经实现了 TemplateModel 接口,所以没有转换(包装)的需要。 (这个技巧当你不想用对象包装器来包装(转换)某些对象时仍然有用。)

2.2 标量

有4种类型的标量:

  • 布尔值
  • 数字
  • 字符串
  • 日期类型(子类型: 日期(没有时间部分),时间或者日期-时间)

每一种标量类型都是 TemplateTypeModel 接口的实现,这里的 Type 就是类型的名称。这些接口只定义了一个方法: type getAsType();。 它返回变量的Java类型(boolean, Number, String 和 Date 各自代表的值)

由于历史遗留的原因,字符串标量的接口是 TemplateScalarModel,而不是 TemplateStringModel。 (因为早期的 FreeMarker 字符串就是标量。)

这些接口的一个细小的实现和 SimpleType 类名在 freemarker.template 包中是可用的。 但是却没有 SimpleBooleanModel 类型;为了代表布尔值, 可以使用 TemplateBooleanModel.TRUE 和 TemplateBooleanModel.FALSE 来单独使用。

由于历史遗留的原因,字符串标量的实现类是 SimpleScalar,而不是 SimpleString

在FTL中标量是一成不变的。当在模板中设置变量的值时, 使用其他的实例来替换 TemplateTypeModel 实例时, 是不用改变原来实例中存储的值的。

"日期" 类型的难点

对于日期类型来说,有一些难题,因为Java API通常不区别 java.util.Date 只存储日期部分(April 4, 2003), 时间部分(10:19:18 PM),或两者都存(April 4, 2003 10:19:18 PM)。 为了用本文正确显示值(或者进行其它确定的操作),FreeMarker必须知道 java.util.Date 的哪个部分存储了有意义上的信息, 哪部分没有被使用(通常是标记为0的)。不幸的是, 通常该信息只是当值从数据库中取得时可用, 因为大多数数据库有独立的日期,时间和日期-时间(又叫做时间戳)类型, java.sql 有3个对应的 java.util.Date 子类和它们相匹配。

TemplateDateModel 接口有两个方法:分别是 java.util.Date getAsDate() 和 int getDateType() 该接口典型的实现是存储一个 java.util.Date 对象, 加上一个整数来辨别子类型。这个整数的值也必须是 TemplateDateModel 接口中的常量之一:DATE, TIME, DATETIME 和 UNKNOWN

关于 UNKNOWN: java.lang 和 java.util 下的类通常被自动转换成 TemplateModel 的实现类,就是所谓的 对象包装器ObjectWrapper(请参考之前的对象包装介绍)。 如果对象包装器要包装 java.util.Date 类, 它不是 java.sql 日期类的实例,那就不能决定子类型是什么, 所以使用 UNKNOWN。之后,如果模板需要使用这个变量, 而且操作也需要子类型,那就会停止执行并抛出错误。为了避免这种情况的发生, 对于那些可能有问题的变量,模板开发人员必须明确地指定子类型,使用内建函数 date, time 或 datetime (比如 lastUpdated?datetime)。请注意, 如果和格式化参数一起使用内建函数 string, 比如foo?string("MM/dd/yyyy"),那么 FreeMarker 就不必知道子类型了。

 

2.3 容器(重要)

2.3.1 哈希表

哈希表是实现了 TemplateHashModel 接口的Java对象TemplateHashModel 有两个方法: TemplateModel get(String key),这个方法根据给定的名称返回子变量, boolean isEmpty(),这个方法表明哈希表是否含有子变量。 get 方法当在给定的名称没有找到子变量时返回null。

TemplateHashModelEx 接口扩展了 TemplateHashModel。它增加了更多的方法,使得可以使用内建函数 values 和 keys 来枚举哈希表中的子变量。

经常使用的实现类是 SimpleHash,该类实现了 TemplateHashModelEx 接口。从内部来说,它使用一个 java.util.Hash 类型的对象存储子变量。 SimpleHash 类的方法可以添加和移除子变量。 这些方法应该用来在变量被创建之后直接初始化。

在FTL中,容器是一成不变的。那就是说你不能添加,替换和移除容器中的子变量。

2.3.2 序列

序列是实现了 TemplateSequenceModel 接口的Java对象。它包含两个方法:TemplateModel get(int index) 和 int size()

经常使用的实现类是 SimpleSequence。该类内部使用一个 java.util.List 类型的对象存储它的子变量。 SimpleSequence 有添加子元素的方法。 在序列创建之后应该使用这些方法来填充序列。

2.3.3 集合

集合是实现了 TemplateCollectionModel 接口的Java对象。这个接口定义了一个方法: TemplateModelIterator iterator()。 TemplateModelIterator 接口和 java.util.Iterator 相似,但是它返回 TemplateModels 而不是 Object, 而且它能抛出 TemplateModelException 异常。

通常使用的实现类是 SimpleCollection

2.4 方法

方法变量在存于实现了 TemplateMethodModel 接口的模板中。这个接口包含一个方法: TemplateModel exec(java.util.List arguments)。 当使用 方法调用表达式 调用方法时,exec 方法将会被调用。 形参将会包含FTL方法调用形参的值。exec 方法的返回值给出了FTL方法调用表达式的返回值。

TemplateMethodModelEx 接口扩展了 TemplateMethodModel 接口。它没有添加任何新方法。 事实上这个对象实现这个 标记 接口是给FTL引擎暗示, 形式参数应该直接以 TemplateModel 的形式放进 java.util.List。否则将会以 String 形式放入list。

2.5 指令

  • 示例 1

  • Java程序员可以使用 TemplateDirectiveModel 接口在Java代码中实现自定义指令。详情可以参加API文档。

  • TemplateDirectiveModel 在 FreeMarker 2.3.11 版本时才加入, 来代替快被废弃的 TemplateTransformModel

  • 示例 2

  • 注意

  • TemplateDirectiveModel 对象通常不应该是有状态的,这一点非常重要。 一个经常犯的错误是存储指令的状态然后在对象的属性中调用执行。 想一下相同指令的嵌入调用,或者指令对象被用作共享变量, 并通过多线程同时访问。

  • 不幸的是, TemplateDirectiveModel 不支持传递参数的位置(而不是参数名称)。从 FreeMarker 2.4 版本开始,它将被修正。

2.6 结点变量

结点变量体现了树形结构中的结点。结点变量的引入是为了帮助用户 在数据模型中处理XML文档 但是它们也可以用于构建树状模型。如需要有关从模板语言角度考虑的结点信息, 那么可以 阅读之前章节

结点变量有下列属性,它们都由 TemplateNodeModel 接口的方法提供:

  • 基本属性:

    • TemplateSequenceModel getChildNodes() 一个结点的子结点序列(除非这个结点是叶子结点,这时方法返回一个空序列或者是null)。 子结点本身应该也是结点变量。

    • TemplateNodeModel getParentNode() 一个结点只有一个父结点(除非这个结点是结点树的根结点, 这时方法返回null)。

  • 可选属性。如果一个属性在具体的使用中没有意义, 那对应的方法应该返回null

    • String getNodeName(): 结点名称也是宏的名称,当使用 recurse 和 visit指令时, 它用来控制结点。因此,如果想通过结点使用这些指令, 那么结点的名称是 必须的

    • String getNodeType():在XML中: "element""text", "comment"等。如果这些信息可用, 就是通过 recurse 和 visit 指令来查找结点的默认处理宏。而且,它对其他有具体用途的应用程序也是有用的。

    • String getNamespaceURI(): 这个结点所属的命名空间(和用于库的FTL命名空间无关)。例如,在XML中, 这就是元素和属性所属XML命名空间的URI。这个信息如果可用,就是通过 recurse 和 visit 指令来查找存储控制宏的FTL命名空间。

    • 在FTL里,结点属性的直接使用可以通过 内建函数 node完成, 还有 visit 和 recurse 宏。

    • 在很多用例中,实现了 TemplateNodeModel 接口和其它接口的变量,因为结点变量属性仅仅提供基本的结点间导航的方法。 需要具体的例子,请参考 FreeMarker如何处理XML 部分。

2.7 对象包装

2.7.1 默认对象包装器

对象包装器是实现了 freemarker.template.ObjectWrapper 接口的类。它的目标是实现Java对象(应用程序中特定类等,比如 String, MapList 实例)和FTL类型系统之间的映射。换句话说, 它指定了模板如何在数据模型(包含从模板中调用的Java方法的返回值)中发现Java对象。 对象包装器作为插件放入 Configuration 中,可以使用 object_wrapper 属性设置 (或者使用Configuration.setObjectWrapper)。

从技术角度来说,FTL类型系统由之前介绍过的 TemplateModel 子接口 (TemplateScalarModelTemplateHashMode, TemplateSequenceModel等)来表示。要映射Java对象到FTL类型系统中, 对象包装器的 TemplateModel wrap(java.lang.Object obj) 方法会被调用。

有时FreeMarker需要撤回映射,此时 对象包装器ObjectWrapper 的 Object unwrap(TemplateModel) 方法就被调用了 (或其他的变化,请参考API文档来获取详细内容)。最后的操作是在 ObjectWrapperAndUnwrapper 中,它是 ObjectWrapper 的子接口。很多实际的包装器会实现 ObjectWrapperAndUnwrapper 接口。

我们来看一下包装Java对象并包含其他对象 (比如 MapList,数组, 或者有JavaBean属性的对象)是如何进行的。可以这么说,对象包装器将 Object[] 数组包装成 TemplateSquenceModel 接口的一些实现当FreeMarker需要FTL序列中项的时候,它会调用 TemplateSquenceModel.get(int index) 方法。该方法的返回值是 TemplateModel,也就是说,TemplateSquenceModel 实现不仅仅可以从给定的数组序列获取 对象, 也可以负责在返回它之前包装该值。为了解决这个问题,典型的 TemplateSquenceModel 实现将会存储它创建的 ObjectWrapper,之后再调用该 ObjectWrapper 来包装包含的值。相同的逻辑代表了 TemplateHashModel 或其他的 TemplateModel,它是其它 TemplateModel 的容器。 因此,通常不论值的层次结构有多深,所有值都会被同一个 ObjectWrapper 包装。(要创建 TemplateModel 的实现类,请遵循这个原则,可以使用 freemarker.template.WrappingTemplateModel 作为基类。)

数据模型本身(root变量)是 TemplateHashModel。 在 Template.process 中指定的root对象将会被在 object_wrapper 配置中设置的对象包装器所包装,并产生一个 TemplateHashModel。从此,被包含值的包装遵循之前描述的逻辑 (比如,容器负责包装它的子实例)。

行为良好的对象包装器都会绕过已经实现 TemplateModel 接口的对象。如果将已经实现 TemplateModel 的对象放到数据模型中 (或者从模板中调用的Java方法返回这个对象),那么就可以避免实际的对象包装。 当特别是通过模板访问创建的值时,通常会这么做。因此,要避免更多上面对象包装的性能问题, 但也可以精确控制模板可以看到的内容(不是基于当前对象包装器的映射策略)。 常见的应用程序使用该手法是使用 freemarker.template.SimpleHash 作为数据模型的根root(而不是Map),当使用 SimpleHash 的 put 方法来填充(这点很重要,它不会复制已经填充并存在的 Map)。这会加快顶层数据模型变量的访问速度。

object_wrapper Configuration 的默认设置是 freemarker.template.DefaultObjectWrapper 实例。除非有特别的需求,那么建议使用这个对象包装器,或者是自定义的 DefaultObjectWrapper 的子类。

会识别大部分基本的Java类型,比如 String, NumberBoolean, DateList (通常还有全部的 java.util.Collection 类型), 数组,Map等。并把它们自然地包装成匹配 TemplateModel 接口的对象它也会使用 freemarker.ext.dom.NodeModel 来包装W3C DOM结点, 所以可以很方便地处理XML, 在XML章节会有描述)。 对于Jython对象,会代理到 freemarker.ext.jython.JythonWrapper 上。 而对于其它所有对象,则会调用 BeansWrapper.wrap(超类的方法), 暴露出对象的JavaBean属性作为哈希表项 (比如FTL中的 myObj.foo 会在后面调用 getFoo()), 也会暴露出对象(比如FTL中的 myObj.bar(1, 2) 就会调用方法) 的公有方法(JavaBean action)。(关于对象包装器的更多信息,请参阅 该章节。)

关于 DefaultObjectWrapper 更多值得注意的细节:

  • 不用经常使用它的构造方法,而是使用 DefaultObjectWrapperBuilder 来创建它。 这就允许 FreeMarker 使用单例。

  • DefaultObjectWrapper 有 incompatibleImprovements 属性, 这在 2.3.22 或更高版本中是极力推荐的(参看该效果的 API文档)。如何来设置:

  • 如果已经在 2.3.22 或更高版本的 Configuration 中设置了 incompatible_improvements 选项, 而没有设置 object_wrapper 选项(那么它就

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值