很少使用的和专家级的内建函数

优质
小牛编辑
127浏览
2023-12-01

这些是你通常情况下不应该使用的内建函数, 但是在特殊情况下(调试,高级宏)它们会有用。 如果你需要在普通页面模板中使用这些函数, 你可能会重新访问数据模型,所以你不要使用它们。

api, has_api

Note:

这些内建函数从 FreeMarker 2.3.22 版本开始存在。

如果value本身支持这个额外的特性, value?api 提供访问 value 的API (通常是 Java API),比如 value?api.someJavaMethod(), 当需要调用对象的Java方法时,这种方式很少使用, 但是 FreeMarker 揭示的value的简化视图的模板隐藏了它,也没有相等的内建函数。 例如,当有一个 Map,并放入数据模型 (使用默认的对象包装器),模板中的 myMap.myMethod() 基本上翻译成Java的 ((Method) myMap.get("myMethod")).invoke(...),因此不能调用 myMethod。如果编写了 myMap?api.myMethod() 来代替,那么就是Java中的 myMap.myMethod()

尽可能地应避免使用 api, 基于FTL类型和相关内建函数的功能。 例如,不要使用 users?api.size(),但可以使用 users?size。使用 ?api 的变化是很累赘的,更慢, 当 FreeMarker 配置设置修改时容易失败,最重要的是, 当数据模型的技术细节改变时更容易失败。例如,如果 usersList 变为数组,那么 users?size 继续有效,而 users?api.size() 就会失败。

由于一些原因,避免调用 修改 对象 (特别是 MapCollection) 或非线程安全的方法。 模板通常也不希望去修改暴露于它们的对象,只是展示它们。 因此应用程序可能会传递一些对象到多个(可能是并发的)模板处理中。

api 内建函数不是到处都可用的, 有一些需求会遇到:

  • api_builtin_enabled 配置设置项必须设置为 true。为了不降低已有应用程序的安全性,它的默认值是 false (至少在 2.3.22 版本中)。

  • 值本身要支持它。我们在讨论当模板看到的值,它是通过 对象包装 从原始对象值(来自于数据模型或者Java方法的返回值)中创建的。 因此,这就依赖 FreeMarker 的配置设置项 object_wrapper, 还有被包装的(原始)对象:

    • 当对象包装器是 DefaultObjectWrapper ,并且它的 incompatibleImprovements 设置为 2.3.22 或更高 (在这里看如何设置它) (事实上,要做的是将它的 useAdaptersForContainer 属性设置为 true,但那是提到的 incompatibleImprovements 的默认值)时,从 MapList 中得到FTL值支持 ?api。其它的 java.util.Collections 也是这样,如果 DefaultObjectWrapperforceLegacyNonListCollections 属性设置为 false (默认是 true, 这是为了更好的向后兼容拆包)。

    • 当被纯 BeansWrapper 包装时,所有值都支持 ?api。但是再次重申,如果有其它方法,就避免使用它。

    • 实现了 freemarker.template.TemplateModelWithAPISupport 接口, 自定义的 TemplateModel 可以支持 ?api

当在配置中不允许或值本身不支持 ?api 时使用了它, 就会中止模板处理并发生错误。

一个值是否支持 ?api 可以通过 value?has_api 来检测, 返回一个布尔值。请注意,?has_api 的结果不受 api_builtin_enabled 设置的影响。

byte, double, float, int, long, short

返回一个包含原变量中相同值的 SimpleNumber, 但是在内部表示值中使用了 java.lang.Type。 如果方法被重载了,这是有用的,或者一个 TemplateModel 解包器在自动选择适合的 java.lang.* 类型有问题时。 请注意,从2.3.9版本开始,解包器有本质上改进, 所以将基本不会使用到这些内建函数来转换数字类型了, 除非在重载方法调用中来解决一些含糊的东西。

内建函数 long 也可以用于日期, 时间和时间日期类型的值来获取返回为 java.util.Date.getTime() 的值。如果你不得不调用使用 long 类型时间戳的Java方法时, 这是非常有用的。这个转换不是自动进行的。

eval

这个函数求一个作为FTL表达式的字符串的值。比如 "1+2"?eval返回数字3。

在调用 eval 的地方, 已被求值的表达式看到相同的变量(比如本地变量)是可见的。 也就是说,它的行为就像在 s?eval 处, 你有 s。除了,指向在 s 之外创建的循环变量,它不能使用 循环变量内建函数

配置设置项影响来自 Configuration 对象表达式解析(比如语法),而不是来自调用 eval 的的模板。

has_content

如果变量(不是Java的 null) 存在而且不是"空"就返回 true,否则返回 false。"空”"含义靠具体的情形来决定。 它是直观的常识性概念。下面这些都是空:长度为0的字符串, 没有子变量的序列或哈希表,一个已经超过最后一项元素的集合。 如果值不是字符串,序列,哈希表或集合,如果它是数字,日期或布尔值 (比如 0false 是非空的), 那么它被认为是非空的,否则就是空的。注意当你的数据模型实现了多个模板模型接口, 你可能会得到不是预期的结果。然而,当你有疑问时你通常可以使用 expr!?size > 0expr!?length > 0 来代替 expr?has_content

这个函数是个特殊的函数,你可以使用像 默认值操作符 那样的圆括号手法。也就是说,你可以编写 product.color?has_content(product.color)?has_content 这样的代码。 第一个没有控制当 product 为空的情形,而第二个控制了。

interpret

该内建函数解析字符串作为FTL模板,而且返回一个用户自定义指令, 也就是当应用于任意代码块中时,执行模板就像它当时 被包含 一样。例如:

<#assign x=["a", "b", "c"]>
<#assign templateSource = r"<#list x as y>${y}</#list>">
<#-- Note: That r was needed so that the ${y} is not interpreted above -->
<#assign inlineTemplate = templateSource?interpret>
<@inlineTemplate />

将会输出:

abc

正如你看到的,inlineTemplate 是用户自定义指令, 也就是当被执行时,运行当时内容是是 templateSource 值的模板。

配置设置项影响来自 Configuration 对象的解析(比如标签语法和命名转换),而不是来自调用 interpret 的模板。 这也就以为着之前的自动探测标签语法或命名转换不会影响已解释模板的解析。 这也和 include 指令 的工作方式一致。

通过 interpret 创建的模板名称就是模板调用 interpret 的名称,并加上 "->anonymous_interpreted"。例如, 如果模板调用的内建函数是 "foo/bar.ftl", 那么结果模板的名称就是 "foo/bar.ftl->anonymous_interpreted"。 因此,在已解释模板中的相对路径也就相对于该路径 (也就是说,根路径就是 "foo"), 而且在已解释模板内部的错误将会指向这个生成的模板名称。

要得到更多有用的错误消息,可以在 "->" 之后覆盖模板名称部分。例如,我们说 mailTemplateSource 来自于数据库表 mail_template,那么在错误时, 想得到包含数据库ID的失败模板的错误日志:

<#assign inlineTemplate = [mailTemplateSource, "mail_templates id=${mailTemplateId}"]?interpret>

正如你看到的,interpret 可以应用于有两项的序列, 这里的第一项是要解释的FTL字符串,第二项是在 "->" 之后使用的模板名称。

is_...

这些内建函数用来检查变量的类型,然后根据类型返回 truefalse。 下面是 is_... 内建函数列表:

内建函数 如果值是 … 时返回 true
is_string 字符串
is_number 数字
is_boolean 布尔值
is_date 不要使用它!使用 is_date_like 来代替, 它们是相同的。往后也许会修改它的含义为 date_only
is_date_like 日期,也就似乎日期,时间或者日期-时间, 亦或者是未知精确类型的日期(从 FreeMarker 2.3.21 版本开始)
is_date_only 日期 (没有时间部分) (从 FreeMarker 2.3.21 版本开始)
is_time 时间 (没有年-月-日部分) (从 FreeMarker 2.3.21 版本开始)
is_datetime 日期-时间 (包含年-月-日和时间两者)
is_unknown_date_like 不清楚是日期或时间或日期-时间的日期
is_method 方法
is_transform 变换
is_macro 宏或函数(当然,由于历史问题,也对函数有效)
is_hash 哈希表 (包含扩展的哈希表)
is_hash_ex 扩展的哈希表 (支持 ?keys?values)
is_sequence 序列
is_collection 集合 (包含扩展的集合)
is_collection_ex 扩展的集合 (支持 ?size)
is_enumerable 序列或集合
is_indexable 序列
is_directive 指令类型 (例如宏 或 TemplateDirectiveModelTemplateTransformModel, 等...), 或者函数 (由于历史问题)
is_node 结点

namespace

该内建函数返回和宏变量或函数变量关联的命名空间 (也就是命名空间的"入口"哈希表)。你只能和宏和函数一起来用它。

new

这是用来创建一个确定的 TemplateModel 实现变量的内建函数。

? 的左边你可以指定一个字符串, 是 TemplateModel 实现类的完全限定名。 结果是调用构造方法生成一个方法变量,然后将新变量返回。

比如:

<#-- Creates an user-defined directive be calling the parameterless constructor of the class -->
<#assign word_wrapp = "com.acmee.freemarker.WordWrapperDirective"?new()>
<#-- Creates an user-defined directive be calling the constructor with one numerical argument -->
<#assign word_wrapp_narrow = "com.acmee.freemarker.WordWrapperDirective"?new(40)>

更多关于构造方法参数被包装和如何选择重载的构造方法信息, 请阅读: 程序开发指南/其它/Bean的包装

该内建函数可以是出于安全考虑的, 因为模板作者可以创建任意的Java对象,只要它们实现了 TemplateModel 接口,然后来使用这些对象。 而且模板作者可以触发没有实现 TemplateModel 接口的类的静态初始化块。你可以(从 2.3.17版开始)使用 Configuration.setNewBuiltinClassResolver(TemplateClassResolver) 或设置 new_builtin_class_resolver 来限制这个内建函数对类的访问。 参考Java API文档来获取详细信息。如果允许并不是很可靠的用户上传模板, 那么你一定要关注这个问题。

number_to_date, number_to_time, number_to_datetime

它们被用来转换数字(通常是Java的 long类型)到日期, 时间或时间日期类型。这就使得它们和Java中的 new java.util.Date(long) 是一致的。那也就是说, 现在数字可以被解释成毫秒数进行参数传递。数字可以是任意内容和任意类型, 只要它的值可以认为是 long 就行。如果数字不是完整的, 那么它就会根据"五入"原则进行进位。这个转换不是自动进行的。

比如:

${1305575275540?number_to_datetime}
${1305575275540?number_to_date}
${1305575275540?number_to_time}

将会输出这样的内容(基于当前的本地化设置和时区):

May 16, 2011 3:47:55 PM
May 16, 2011
3:47:55 PM