(三)freemarker模板开发 续

来源:互联网 发布:javascript初级教程 编辑:程序博客网 时间:2024/04/30 14:06

1、自定义指令

自定义指令可以使用macro指令来定义,这是模板设计者所关心的内容。Java程序员若不想在模板中实现定义指令,而是在Java语言中实现指令的定义,这时可以使用freemarker.template.TemplateDirectiveModel类来扩展

宏是有一个变量名的模板片段。你可以在模板中使用宏作为自定义指令,这样就能进行重复性的工作。例如,创建一个宏变量来打印大号的”Hello Joe!”。

<#macro greet>
    <font size="+2">Hello Joe!</font>
</#macro>

macro指令自身不打印任何内容,它只是用来创建宏变量,所以就会有一个名为greet的变量。在<#macro greet>和</#macro>之间的内容(称为宏定义体),当使用它作为指令时将会被执行。你可以在FTL标记中通过@代替#来使用自定义指令。使用变量名作为指令名。而且,自定义指令的结束标记也是需要的。那么,就可以这样来使用greet宏了:

<@greet></@greet>

也可以这样写:<@greet />

宏能做的事情还有很多,因为在<#macro ...>和</#macro>之间的东西是模板片段,也就是说它可以包含插值(${...})和FTL标签(如<#if ...>...</#if>)

注意:程序员通常将使用<@...>,这称为宏调用。

2、宏的参数

改进greet宏使之可以使用任意的名字,而不仅仅是“Joe”。为了实现这个目的,就要使用到参数。。在macro指令中,宏名称的后面位置是用来定义变量的。这里我们仅在greet宏中定义一个变量,person:

<#macro greet person>
    <font size="+2">Hello ${person}!</font>
</#macro>

使用这个宏的方法:<@greet person="Fred" />

宏参数的真实值是可以作为变量(person)放在宏定义体中的。使用预定义指令时,参数的值(=号后边的值)可以是FTL表达式。这样,不像HTML,"Fred"和"Batman"的引号就可以不用要了。<@greet person=Fred/>也意味着使用变量的值Fred作为person参数,而不是字符串"Fred"。当然参数值并不一定是字符串类型,也可以是数字,布尔值,哈希表,序列等…也可以在=号左边使用复杂表达式(比如someParam=(price + 50)*1.25)。自定义指令可以有多个参数。如下所示,再添加一个新的参数color:

<#macro greet person color>
   <font size="+2" color="${color}">Hello ${person} !</font>
</#macro>

那么,这个宏就可以这样来使用:<@greet person="Fred" color="black"/>。参数的顺序不重要,下面的这个和上面的含义也是相同的。

<@greet color="black" person="Fred"/>

当调用这个宏的时候,你仅仅可以使用在macro指令中定义的参数(这个例子中是:person和color)。那么当你尝试<@greet person="Fred" color="black" background="green"/>的时候就会发生错误,因为并没有在<#macro ...>中定义参数background。
同时也必须给出在宏中定义所有参数的值。如果你尝试<@greet person="Fred"/>时也会发生错误,因为忘记指定color的值了。很多情况下需要给一个参数指定一个相同的值,所以我们仅仅想在这个值发生变化后重新赋给变量。那么要达到这个目的,在macro指令中必须这么来指定变量:param_name=usual_value。例如,当没有特定值的时候,我们想要给color赋值为"black",那么greet指令就要这么来写:

<#macro greet person color="black">
<font size="+2" color="${color}">Hello ${person}!</font>
</#macro>

现在,我们这么使用宏就可以了:<@greet person="Fred"/>,因为它和<@greet person="Fred" color="black"/>是相同的,这样参数color的值就是已知的了。如果想给color设置为”red”,那么就写成:<@greet person="Fred" color="red"/>,这时macro指令就会使用这个值来覆盖之前设置的通用值,参数color的值就会是”red”了。

根据FTL表达式规则,明白下面这一点是至关重要的,someParam=foo和someParam="${foo}"是不同的。第一种情况,是把变量foo的值作为参数的值来使用。第二种情况则是使用插值形式的字符串,那么参数值就是字符串了,这个时候,foo的值呈现为文本,而不管foo是什么类型(数字,日期等)的。看下面这个例子:someParam=3/4和someParam="${3/4}"是不同的,如果指令需要someParam是一个数字值,那么就不要用第二种方式。切记不要改变这些。
宏参数的另外一个重要的方面是它们是局部变量。

3、嵌套内容

自定义指令可以嵌套内容,和预定义指令相似:<#if ...>nested content</#if>。比如,下面这个例子中是创建了一个可以为嵌套的内容画出边框:

<#macro border>
<table border=4 cellspacing=0 cellpadding=4><tr><td>
<#nested>
</td></tr></table>
</#macro>

<#nested>指令执行位于开始和结束标记指令之间的模板代码段。如果这样写:

<@border>The bordered text</@border>,那么就会输出:

<table border=4 cellspacing=0 cellpadding=4><tr><td>
The bordered text
</td></tr></table>

nested指令也可以多次被调用,例如:

<#macro do_thrice>
  <#nested>
  <#nested>
  <#nested>
</#macro>

调用:

<@do_thrice>
Anything.
</@do_thrice>

输出:

Anything.
Anything.
Anything.

如果不使用nested指令,那么嵌套的内容就会被执行,如果不小心将greet指令写成了这样:

<@greet person="Joe">
Anything.
</@greet>

FreeMarker不会把它视为错误,只是打印:<font size="+2">Hello Joe!</font>

嵌套的内容被忽略了,因为greet宏没有使用nested指令。
嵌套的内容可以是任意有效的FTL,包含其他的用户自定义指令,这样也是对的:

<@border>
<ul>
<@do_thrice>
<li><@greet person="Joe"/>
</@do_thrice>
</ul>
</@border>

将会输出:

<table border=4 cellspacing=0 cellpadding=4><tr><td>
<ul>
<li><font size="+2">Hello Joe!</font>
<li><font size="+2">Hello Joe!</font>
<li><font size="+2">Hello Joe!</font>
</ul>
</tr></td></table>

在嵌套的内容中,宏的局部变量是不可见的。为了说明这点,我们来看:

<#macro repeat count>
<#local y = "test">
<#list 1..count as x>
${y} ${count}/${x}: <#nested>
</#list>
</#macro>
<@repeat count=3>${y!"?"} ${x!"?"} ${count!"?"}</@repeat>

将会打印:

test 3/1: ? ? ?
test 3/2: ? ? ?
test 3/3: ? ? ?

因为y,x和count是宏的局部(私有)变量,从宏外部定义是不可见的。此外不同的局部变量的设置是为每个宏自己调用的,所以不会导致混乱:

<#macro test foo>${foo} (<#nested>) ${foo}</#macro>

<@test foo="A"><@test foo="B"><@test foo="C"/></@test></@test>

将会打印:A (B (C () C) B) A

4、宏和循环变量

自定义指令也可以有循环变量。比如我们来扩展先前例子中的do_thrice指令,就可以拿到当前的循环变量的值。而对于预定义指令(如list),当使用指令(就像<#list foos as foo>...</#list>中的foo)时,循环变量的名字是已经给定的,变量值的设置是由指令本身完成的。

<#macro do_thrice>
<#nested 1>
<#nested 2>
<#nested 3>
</#macro>
<@do_thrice ; x> <#-- 用户自定义指令 使用";"代替"as" -->
${x} Anything.
</@do_thrice>

输出:

1 Anything.
2 Anything.
3 Anything.

nested指令(当然参数可以是任意的表达式)的参数。循环变量的名称是在自定义指令的开始标记(<@...>)的参数后面通过分号确定的。
一个宏可以使用多个循环变量(变量的顺序是很重要的):

<#macro repeat count>
<#list 1..count as x>
<#nested x, x/2, x==count>
</#list>
</#macro>
<@repeat count=4 ; c, halfc, last>
${c}. ${halfc}<#if last> Last!</#if>
</@repeat>

那么,将会输出:

1. 0.5
2. 1
3. 1.5
4. 2 Last!

在自定义指令的开始标签(分号之后)为循环变量指定不同的数字是没有问题的,而不能在nested指令上使用。如果在分号之后指定的循环变量少,那么就看不到nested指令提供的最后的值,因为没有循环变量来存储这些值,下面的这些都是可以的:

<@repeat count=4 ; c, halfc, last>
${c}. ${halfc}<#if last> Last!</#if>
</@repeat>
<@repeat count=4 ; c, halfc>
${c}. ${halfc}
</@repeat>
<@repeat count=4>
Just repeat it...
</@repeat>

如果在分号后面指定了比nested指令还多的变量,那么最后的循环变量将不会被创建(在嵌套内容中不会被定义)。

5、在模板中定义变量

正如我们已经描述过的,模板可以使用在数据模型中定义的变量。在数据模型之外,模板本身也可以定义变量来使用。这些临时变量可以使用FTL指令来创建和替换。要注意每一次模板执行时都维护它自己的这些变量的私有设置,这些变量是在页面用以呈现信息的。变量的初始值是空,当模板执行结束这些变量便被销毁了。
你可以访问一个在模板里定义的变量,就像是访问数据模型根上的变量一样。这个变量比定义在数据模型中的同名参数有更高的优先级,那就是说,如果你恰巧定义了一个名为”foo”的变量,而在数据模型中也有一个名为”foo”的变量,那么模板中的变量就会将数据模型根上的变量隐藏(而不是覆盖!)。例如${foo}将会打印在模板中定义的变量。
在模板中可以定义三种类型的变量:

 简单变量:它能从模板中的任何位置来访问,或者从使用include指令引入的模板访问。可以使用assign或macro指令来创建或替换这些变量。
 局部变量:它们只能被设置在宏定义体内,而且只在宏内可见。一个局部变量的生存周期只是宏的调用过程。可以使用local指令在宏定义体内创建或替换局部变量。
 循环变量:循环变量是由指令(如list)自动创建的,而且它们只在指令的开始和结束标记内有效。宏的参数是局部变量而不是循环变量。

示例:使用assign创建和替换变量

<#assign x = 1> <#-- 创建变量 x -->
${x}
<#assign x = x + 3> <#-- 替换变量 x -->
${x}

输出为:

1
4

局部变量也会隐藏(不是覆盖)同名的简单变量。循环变量也会隐藏(不是覆盖)同名的局部变量和简单变量。例如:

<#assign x = "plain">
1. ${x} <#-- 这里是普通变量 -->
<@test/>
6. ${x} <#-- 普通变量的值没有被改变 -->
<#list ["loop"] as x>
7. ${x} <#-- 现在循环变量隐藏了普通变量 -->
<#assign x = "plain2"> <#-- 替换普通变量, 隐藏在这里不起作用-->
8. ${x} <#-- 它仍然隐藏普通变量 -->
</#list>
9. ${x} <#-- 普通变量的新值 -->
<#macro test>
2. ${x} <#-- 这里我们仍然看到的是普通变量 -->
<#local x = "local">
3. ${x} <#-- 现在局部变量隐藏了它 -->
<#list ["loop"] as x>
4. ${x} <#-- 现在循环变量隐藏了局部变量 -->
</#list>
5. ${x} <#-- 现在又可以看到局部变量了 -->
</#macro>

输出为:

1. plain
2. plain
3. local
4. loop
5. local
6. plain
7. loop
8. loop
9. plain2

内部循环变量可以隐藏外部循环变量:

<#list ["loop 1"] as x>
${x}
<#list ["loop 2"] as x>
${x}
<#list ["loop 3"] as x>
${x}
</#list>
${x}
</#list>
${x}
</#list>

输出为:

loop 1
loop 2
loop 3
loop 2
loop 1

注意到循环变量的设置是通过指令调用时创建的(本例中的<list ...>标签)。没有其他的方式去改变循环变量的值(也就是说你不能使用定义指令来改变它的值。)。从上面的示例来看,尽管也可以使用一个循环变量来隐藏另外一个。
有时会发生一个变量隐藏数据模型中的同名变量,但是如果想访问数据模型中的变量,就可以使用特殊变量globals。例如,假设我们在数据模型中有一个名为user,值为”Big Joe”的变量。

<#assign user = "Joe Hider">
${user} <#-- 打印: Joe Hider -->
${.globals.user} <#-- 打印: Big Joe -->

6、命名空间

当运行FTL模板时,就会有使用assign和macro指令创建的变量的集合(可能是空的),像这样的变量集合被称为namespace命名空间。在简单的情况下可以只使用一个命名空间,称之为main namespace主命名空间。因为通常只使用本页上的命名空间,所以就没有意识到这点。

如果想创建可以重复使用的宏,函数和其他变量的集合,通常用术语来说就是引用library库。使用多个命名空间是必然的。只要考虑你在一些项目中,或者想和他人共享使用的时候,你是否有一个很大的宏的集合。但要确保库中没有宏(或其他变量)名和数据模型中变量同名,而且也不能和模板中引用其他库中的变量同名。通常来说,变量因为名称冲突也会相互冲突。所以要为每个库中的变量使用不同的命名空间。

6.1 创建一个库

建立一个简单的库。假设你需要通用的变量copyright和mail(在你疑问之前,宏当作是变量)

<#macro copyright date>
<p>Copyright (C) ${date} Julia Smith. All rights reserved.</p>
</#macro>
<#assign mail = "jsmith@acme.com">

把上面的这些定义存储在文件lib/my_test.ftl中(目录是你存放模板的位置)。假设想在aWebPage.ftl中使用这个模板。如果在aWebPage.ftl使用<#include "/lib/my_test.ftl">,那么就会在主命名空间中创建两个变量,这样就不是很好,因为想让它们只在同一个命名空间”My Test Library”中。所以就不得不使用import指令来代替include了。乍一看,这个指令和include很相似,但是它会为lib/my_test.ftl创建一个空的命名空间,然后在那里执行。lib/my_test.ftl会发现它自己在一个新的环境中,那里只有数据模型的变量可以来呈现(因为它们在那里面都是可见的),然后会在这个环境中创建两个变量。现在来看这很不错,但是如果想访问aWebPage.ftl中的两个变量,而它们使用的是主命名空间,就不能看到其他命名空间中的变量。解决方法是import指令不仅仅创建命名空间,而且要通过import的调用者(本例中的主命名空间)创建一个新的哈希表变量,这就成为进入新的命名空间的大门。那么aWebPage.ftl就像下面这样:

<#import "/lib/my_test.ftl" as my>
<#-- 被称为"my"的哈希表就会是那个"大门" -->
<@my.copyright date="1999-2002"/>
${my.mail}

要注意它是怎么访问为lib/my_test.ftl创建的命名空间中的变量的,通过新创建的哈希表,my。那么将会打印出:

<p>Copyright (C) 1999-2002 Julia Smith. All rights reserved.</p>
smith@acme.com

如果在主命名空间中有一个变量,名为mail或copyright,那么就不会引起混乱了,因为两个模板使用了不同的命名空间。例如,在lib/my_test.ftl中修改copyright成如下这样:

<#macro copyright date>
<p>Copyright (C) ${date} Julia Smith. All rights reserved.
<br>Email: ${mail}</p>
</#macro>

然后修改aWebPage.ftl中的内容:

<#import "/lib/my_test.ftl" as my>
<#assign mail="fred@acme.com">
<@my.copyright date="1999-2002"/>
${my.mail}
${mail}

那么将会输出:

<p>Copyright (C) 1999-2002 Julia Smith. All rights reserved.
<br>Email: smith@acme.com</p>
jsmith@acme.com
fred@acme.com

当调用了copyright宏之后,输出和上面的是相似的,因为FreeMarker已经暂时转向由import指令为/lib/my_test.ftl生成的命名空间了。因此,copyright宏看到在主命名空间中变量mail存在,而且其他的mail不存在。

6.2、在引入的命名空间上编写变量

偶尔想要在一个被包含的命名空间上创建或替换一个变量。那么可以使用assign指令在完成,如果用到了它的namespace变量,例如下面这样:

<#import "/lib/my_test.ftl" as my>
${my.mail}
<#assign mail="jsmith@other.com" in my>
${my.mail}

将会输出:

jsmith@acme.com
jsmith@other.com

6.3、命名空间和数据模型

数据模型中的变量在任何位置都是可见的。如果在数据模型中有一个名为user的变量,那么lib/my_test.ftl也能访问它,aWebPage.ftl当然也能。

<#macro copyright date>
<p>Copyright (C) ${date} ${user}. All rights reserved.</p>
</#macro>
<#assign mail = "${user}@acme.com">

如果user是”Fred”的话,下面这个例子:

<#import "/lib/my_test.ftl" as my>
<@my.copyright date="1999-2002"/>
${my.mail}

将会输出:

<p>Copyright (C) 1999-2002 Fred. All rights reserved.</p>
Fred@acme.com

不要忘了在模板的命名空间(可以使用assign或macro指令来创建的变量)中的变量有着比数据模型中的变量更高的优先级。因此,数据模型的内容不会干涉到由库创建的变量。
注意:
在通常一些应用中,你也许想在模板中创建所有命名空间都可见的变量,就像数据模型中的变量一样。但是你不能在模板中改变数据模型,却可以通过global指令来达到相似的效果

6.4、命名空间的生命周期

命名空间由使用的import指令中所写的路径来识别。如果想多次import这个路径,那么只会为第一次的import引用创建命名空间执行模板。后面相同路径的import只是创建一个哈希表当作访问相同命名空间的“门”。例如,在aWebPage.ftl中:

<#import "/lib/my_test.ftl" as my>
<#import "/lib/my_test.ftl" as foo>
<#import "/lib/my_test.ftl" as bar>
${my.mail}, ${foo.mail}, ${bar.mail}
<#assign mail="jsmith@other.com" in my>
${my.mail}, ${foo.mail}, ${bar.mail}

将会输出:

jsmith@acme.com, jsmith@acme.com, jsmith@acme.com
jsmith@other.com, jsmith@other.com, jsmith@other.com

这里可以看到通过my,foo和bar访问相同的命名空间。

还要注意命名空间是不分层次的,它们相互之间是独立存在的。那么,如果在命名空间N1中import命名空间N2,那N2也不在N1中,N1只是可以通过哈希表来访问N2。这和在主命名空间中importN2,然后直接访问命名空间N2是一样的过程。
每一次模板的执行过程,它都有一个私有的命名空间的集合。每一次模板执行工作都是一个分离且有序的过程,它们仅仅存在一段很短的时间,同时页面用以呈现内容,然后就和所有填充过的命名空间一起消失了。因此,无论何时我们说第一次调用import,一个单一模板执行工作的内容都是这样。

6.5、为他人编写库

如果你已经为其他人员编写一个有用的,高质量的库,你也许想把它放在网络上(就像http://freemarker.org/libraries.html)。为了防止和其他作者使用库的命名相冲突,而且引入其他库时要书写简单,这有一个事实上的标准,那就是指定库路径的格式。这个标准是:库的路径必须对模板和其他库可用(可引用),就像这样:
/lib/yourcompany.com/your_library.ftl
如果你为Example公司工作,它们拥有www.example.com网的主页,你的工作是开发一个部件库,那么要引入你所写的FTL的路径应该是:
/lib/example.com/widget.ftl
注意到www已经被省略了。第三次路径分割后的部分可以包含子目录,可以像下面这样写:
/lib/example.com/commons/string.ftl
一个重要的规则就是路径不应该包含大写字母,为了分隔词语,使用下划线_,就像wml_form(而不是wmlForm)。
如果你的工作不是为公司或组织开发库,也要注意,你应该使用项目主页的URL,比如/lib/example.sourceforge.net/example.ftl或/lib/geocities.com/jsmith/example.ftl。

7、空白处理

在运行中,模板中的空白在某种程度上来说是纠缠所有模板引擎的一个问题。看下面的例子

<p>List of users:[BR] 
<#assign users = [{"name":"Joe", "hidden":false},[BR]
                                 {"name":"James Bond", "hidden":true},[BR]
                                 {"name":"Julia", "hidden":false}]>[BR] 
<ul>[BR] 
<#list users as user>[BR]
  <#if !user.hidden>[BR] 
  <li>${user.name}[BR] 
  </#if>[BR] 
</#list>[BR] 
</ul>[BR] 
<p>That's all.

如果FreeMarker能按照规则输出所有的问题,将会有很多的不想要的空格和换行,幸运的是,HTML和XML都不是对空白敏感的,但是这么多多余的空白是很令人头疼的,而且增加处理后的HTML文件大小也是没必要的。当然,对于空白敏感的方式的输出这依旧是个大问题。
FreeMarker提供下面的工具来处理这个问题:
 ◆忽略某些模板文件的空白的工具(解析阶段空白就被移除了):
       ■剥离空白:这个特性会自动忽略在FTL标签周围多余的空白。这个特性可以通过模板来随时使用和禁用。
       ■微调指令:t,rt和lt,使用这些指令可以明确地告诉FreeMarker去忽略某些空白。可以阅读参考手册来获取更多信息。
       ■FTL参数strip_text:这将从模板中删除所有顶级文本。对模板来说这很有用,它只包含某些定义的宏(还有以他一些没有输出的指令),因为它可以移除宏定义和其他顶级指令中的换行符,这样可以提高模板的可读性。
◆从输出中移除空白的工具(移除临近的空白):
       ■ compress指令

剥离空白:

如果对于模板来说使这个特性成为可能的话,那么它就会自动忽略(也就是不在输出中打印出来)两种典型的多余空白:
■ 缩进空白和在行末尾的尾部空白(包括换行符)将会被忽略,只会留下FTL标签(比如<@myMacro/>,<#if ...>)和FTL注释(如<#-- blah -->),除了被忽略的空白本身。例如,如果一行只包含一个<#if ...>,那么在标签前面的缩进和标签后面的换行符将会被忽略。然而,如果这行上包含<#if ...>x,那么空白就不会被忽略,因为这个x不是FTL标签。注意,根据这些规则,一行上包含<#if ...><#list ...>,空白就会被忽略,而一行上有<#if ...> <#list ...> 这样的就不会,因为在两个FTL标签之间的空白是嵌入的空白,而不是缩进的或尾部空白。
■加在下面这些指令之间的空白会被忽略:macro,function,assign,global,local,ftl,import,但也是仅仅指令之间只有一个空白或FTL注释。实际应用中,它意味着你可以在宏定义和参数定义之间放置空行,因为行间距是为了更好的可读性,不包括打印不必要的空行(换行符)。

剥离空白功能可以通过ftl指令在模板中开启或关闭。如果你没有通过ftl指令来指定,那么剥离空白功能是开启还是关闭就依据程序员是如何设置FreeMarker的了。默认的情况下剥离空白是开启的,程序员可以留着不管(建议这样做)

剥离空白可以为单独的一行关闭,就是使用nt指令(对没有去掉空白的行来说)。

使用compress指令:

另外一种方法就是使用compress指令,和剥离空白相反,这个工作是直接基于生成的输出内容,而不是对于模板进行。也就是说,它会动态地检查输出内容,而不会检查生成输出FTL的程序。它会很强势地移除缩进,空行和重复的空格/制表符

注意compress是完全独立于剥离空白特性的。所以它剥离模板中的空白是可能的。那么之后输出的内容就是被压缩过的。
此外,在默认情况下,名为compress的用户自定义指令是可以在数据模型中存在的(由于向下兼容特性)。这和指令是相同的,除了可以选择设置single_line属性,这将会移除所有的介于其中的换行符。

8、替换(方括号)语法

这个特性从FreeMarker 2.3.4版本后才可用。
FreeMarker支持一个替换的语法。就是在FreeMarker的指令和注释中用[和]来代替<和>,例如下面这个例子:
 调用预定义指令:[#list animals as being]...[/#list]
 调用自定义指令:[@myMacro /]
 注释:[#-- the comment --]
为了使用这种语法从而代替默认语法,从模板开始,使用ftl指令都要使用这用语法。如果你不知道什么是ftl指令,那么就用[#ftl]来开始模板,要记住这个要放在文件的最前面(除了它前面的空格)。例如:

[#ftl]
<p>We have these animals:
<table border=1>
<tr><th>Name<th>Price
[#list animals as being]
<tr>
<td>
[#if being.size = "large"]<b>[/#if]
${being.name}
[#if being.size = "large"]</b>[/#if]
<td>${being.price} Euros
[/#list]
</table>

这种替换语法(方括号)和默认语法(尖括号)在一个模板中是相互排斥的。那就是说,整个模板要么全部使用替换语法,要么全部使用默认语法。如果模板使用了替换语法,那么如<#if ...>这样的部分就会被算作是静态文本,而不是FTL标签了。类似地,如果模板使用默认语法,那么如[#if ...]这样的也会被算作是静态文本,而不是FTL标签。
如果你以[#ftl ...](...代表可选的参数列表,当然仅用[#ftl]s也行)来开始文件,那文件就会使用替换(方括号)语法。如果使用<#ftl ...>来开始,那么文件就会使用正常(尖括号)语法。如果文件中没有ftl指令,那么程序员可以通过配置FreeMarker(程序员参看API文档的Configuration.setTagSyntax(int)来使用)来决定使用哪种语法。但是程序员可能使用默认配置。FreeMarker 2.3.x版本默认配置使用常规语法。而2.4版本中的默认配置将会自动检测,也就是说第一个FreeMarker标签决定了语法形式(它可以是任意的,而不仅仅是ftl)。

0 0