一. assign指令
1. 使用该指令你可以创建一个新的变量或者替换一个已经存在的变量。
2. 语法
<#assign name1=value1 name2=value2 ... nameN=valueN>
或
<#assign same as above... in namespacehash>
或
<#assign name>
capture this
</#assign>
或
<#assign name in namespacehash>
capture this
</#assign>
2.1. 变量的名字。
2.2. value: 存储的值。是表达式。
2.3. namespacehash: (通过import)为命名空间创建的哈希表。是表达式。
3. 通常它在当前的命名空间(也就是和标签所在模板关联的命名空间)中创建变量。但如果你是用了in namespacehash, 那么你可以用另外一个命名空间来创建/替换变量。
<#import "/lib/copyright.html" as cr>
<#assign dateStart = "2000" in cr>
${cr.dateStart}
4. assign的极端使用是当它捕捉它的开始标记和结束标记中间生成的输出时。也就是说, 在标记之间打印的东西将不会在页面上显示, 但是会存储在变量中。
<#assign names>
<#list ["张三", "李四", "王五"] as name>
${name}<br />
</#list>
</#assign>
${names}
二. import指令
1. 引入一个库。也就是说, 它创建一个新的空命名空间, 然后在那个命名空间中执行给定path参数中的模板, 所以模板用变量(宏, 函数等)填充命名空间。然后使得新创建的命名空间对哈希表的调用者可用。这个哈希表变量将会在命名空间中, 由import的调用者被创建成一个普通变量, 名字就是hash参数给定的。
2. 语法
<#import path as hash />
2.1. path: 模板的路径。这是一个算作是字符串的表达式。
2.2. hash: 访问命名空间的哈希表变量不带引号的名字。不是表达式。
3. 命名空间由使用import指令中所写的路径来识别。如果想多次import这个路径, 那么只会为第一次import引用创建命名空间并执行模板。后面相同路径的import只是创建一个哈希表当作访问相同命名空间的"门"。
copyright.html
<#assign dateEnd = "2050">
directive.html
<#import "/lib/copyright.html" as cr>
<#import "/lib/copyright.html" as cpyrgt>
<#import "/lib/copyright.html" as copyright>
${cr.dateEnd}, ${cpyrgt.dateEnd}, ${copyright.dateEnd}<br />
<#assign dateEnd="2080" in cr>
${cr.dateEnd}, ${cpyrgt.dateEnd}, ${copyright.dateEnd}
输出:
2050, 2050, 2050
2080, 2080, 2080
4. path参数可以是一个相对路径, 比如: "foo.ftl"和"../foo.ftl"或者是像"/foo.ftl"一样的绝对路径。相对路径是相对于使用import指令模板的目录。绝对路径是程序员配置FreeMarker时定义的相对于根路径的路径。
三. macro, nested, return指令
1. 创建一个宏变量。
2. 语法
<#macro name param1 param2 ... paramN>
...
<#nested var1, var2, ..., varN>
...
<#return>
...
</#macro>
或
<#macro name param1 param2 paramN...>
...
<#nested var1, var2, ..., varN>
...
<#return>
...
</#macro>
2.1. name: 宏变量的名称, 它不是表达式。它可以被写成字符串的形式, 如果宏名称中包含保留字符时, 这是很有用的。
2.2. param1, param2, ... paramN: 局部变量的名称, 存储参数的值(不是表达式)。局部变量可以跟等号, 在等号后面添加默认值。默认值也可以是另外一个参数, 比如: <#macro section title label=title>。
2.3. paramN, 最后一个参数, 可能会有三个点(...), 这就意味着宏接受可变数量的参数, 不匹配其它参数的参数可以作为最后一个参数(也被称作笼统参数)。当宏被命名参数调用, paramN将会是包含宏的所有未声明的键/值对的哈希表。当宏被位置参数调用, paramN将是额外参数的序列。
2.4. var1, var2, ..., varN是nested指令为嵌套内容创建的参数。这些都是表达式。
2.5. return和nested指令是可选的, 而且可以在<#macro ...>和</#macro>之间被用在任意位置和任意次数。
2.6. 没有默认值的参数必须在有默认值参数(paramName=defaultValue)之前。
3. 宏变量存储模板片段(称为宏定义体)可以被用作自定义指令。这个变量也存储自定义指令的被允许的参数名。当你将这个变量作为指令时, 你必须给所有参数赋值, 除了有默认值的参数。默认值当且仅当你调用宏而不给参数赋值时起作用。
4. 变量会在模板开始时被创建; 而不管macro指令放置在模板的什么位置。因此, 这样也可以:
<@test/>
<#macro test>
Test text
</#macro>
5. 有参数的宏
<#macro paramTest foo bar baaz>
ParamTest paramTest, and the params: ${foo}, ${bar}, ${baaz}
</#macro>
<@paramTest foo="a" bar="b" baaz=5*5-2/>
6. 有参数和默认值参数的宏
<#macro paramDefaultValueTest foo bar="Bar" baaz=-1>
ParamDefaultValueTest paramDefaultValueTest, and the params: ${foo}, ${bar}, ${baaz}
</#macro>
<@paramDefaultValueTest foo="a" bar="b" baaz=5*5-2/><br />
<@paramDefaultValueTest foo="a" bar="b"/><br />
<@paramDefaultValueTest foo="a" baaz=5*5-2/><br />
<@paramDefaultValueTest foo="a"/>
7. 可变参数和命名参数的宏
<#macro img src extra...>
<img src="${src?html}"
<#list extra?keys as attr>
${attr}="${extra[attr]?html}"
</#list>
>
</#macro>
<@img src="images/01.png" width=113 height=113 alt="01.png"/>
8. 可变参数的宏, 不管是否使用命名
<#macro m a b ext...>
a = ${a}<br />
b = ${b}<br />
<#if ext?is_sequence>
<#list ext as e>
${e?index} = ${e}<br />
</#list>
<#else>
<#list ext?keys as k>
${k} = ${ext[k]}<br />
</#list>
</#if>
</#macro>
<@m 1 2 3 4 5 /><br />
<@m a=1 b=2 c=3 d=4 e=5 data\-foo=6 myns\:bar=7 />
9. nested
9.1. nested指令执行宏定义体开始和结束标签中间的模板片段。嵌套的片段可以包含模板中任意合法的内容: 插值, 指令等... 它在上下文环境中被执行, 也就是宏被调用的地方, 而不是宏定义体的上下文中。如果你没有调用nested指令, 宏定义体开始和结束标记中的部分将会被忽略。
9.2. 嵌套内容没有参数
<#macro do_twice>
<#nested><br />
<#nested>
</#macro>
<@do_twice>something</@do_twice>
9.3. 嵌套内容使用参数
<#macro repeat count>
<#list 1..count as x>
<#nested x, x/2, x==count><br />
</#list>
</#macro>
<@repeat count=4 ; c, halfc, last>
${c}. ${halfc}<#if last> Last!</#if><br />
</@repeat>
10. return
10.1. 使用return指令, 你可以在任意位置终止一个宏或函数执行。
<#macro testReturn>
TestReturn testReturn
<#return>
Will not be printed.
</#macro>
<@testReturn/>
四. t, lt, rt指令
1. <#t /><#lt /><#rt />忽略标记中行的特定的空白。
2. t(整体削减): 忽略本行中首和尾的所有空白。
3. lt(左侧削减): 忽略本行中首部所有的空白。
4. rt(右侧削减): 忽略本行中尾部所有的空白。
5. "首部空白"表示: 在第一个非空白字符之前, 本行所有空格和制表符。
6. "尾部空白"表示: 在最后一个非空白字符之后, 本行所有的空格和制表符, 还有行末尾的换行符。
7. 这些指令在行内的放置不重要。也就是说, 不管你是将它们放在行的开头, 或是行的末尾, 或是在行的中间, 效果都是一样的。
8. 理解这些检查模板本身的指令是很重要的, 也就是说, 空白的移除发生在解析阶段。
五. ftl指令
1. 语法
<#ftl param1=value1 param2=value2 ... paramN=valueN />
1.1. param1, param2等: 参数的名字, 不是表达式。允许的参数有: encoding, strip_whitespace, strip_text等。
1.2. value1, value2等: 参数的值。必须是一个常量表达式(如: true或"ISO-8859-5"或{x:1, y:2})。它不能用变量。
2. 告诉FreeMarker和其他工具关于模板的信息, 而且帮助程序员来自动检测一个文本文件是否是FTL文件。该指令, 如果存在, 必须是模板的第一句代码。该指令前的任何空白将被忽略。
3. ftl设置有最高的优先级, 也就是说, 它们直接作用于模板而不管其他任何FreeMarker的配置设置。
4. encoding参数: 使用它, 你可以在模板文件本身中为模板指定编码方式(字符集)。
5. strip_whitespace参数: 这将开启/关闭空白剥离。合法的值是布尔值常量true和false(为了向下兼容, 字符串"yes", "no", "true", "false"也是可以的)。默认值(也就是当你不使用这个参数时)是依赖于程序员对FreeMarker的配置, 但是对新的项目还应该是true。
6. strip_text参数: 当开启它时, 当模板被解析时模板中所有顶级文本被移除。这个不会影响宏, 指令或插值中的文本。合法值是布尔值常量true和false(为了向下兼容, 字符串"yes", "no", "true", "false"也是可以的)。默认值(也就是当你不使用这个参数时)是false。
7. strict_syntax参数: 这会开启/关闭"严格的语法"。合法值是布尔值常量true和false(为了向下兼容, 字符串"yes", "no", "true", "false"也是可以的)。默认值(也就是当你不使用这个参数时)是依赖于程序员对FreeMarker的配置, 但是对新的项目还应该是true。
8. ns_prefixes参数: 这是关联结点命名空间前缀的哈希表。这通常是用于XML处理的。
9. attributes参数: 这是关联模板任意属性(名-值对)的哈希表。属性的值可以是任意类型(字符串, 数字, 序列等)。FreeMarker不会尝试去理解属性的含义。它是由封装FreeMarker(比如Web应用框架)的应用程序决定的。因此, 允许的属性的设置是它们依赖的应用(Web应用框架)的语义。程序员: 你可以通过关联Template对象的getCustomAttributeNames和getCustomAttribute方法(从freemarker.core.Configurable继承而来)获得属性。如当模板被解析时, 关联Template对象的模板属性, 属性可以在任意时间被读取, 而模板不需要被执行。上面提到的方法返回未包装的属性值, 也就是说, 使用FreeMarker独立的类型, 如java.util.List。
六. compress指令
1. 语法
<#compress>
...
</#compress>
2. 当你使用了对空白不敏感的格式时压缩指令对于移除多余的空白是很有用的。它捕捉在指令体(也就是在开始标签和结束标签中)中生成的内容, 然后缩小所有不间断的空白序列到一个单独的空白字符。如果被替代的序列包含换行符或是一段空间, 那么被插入的字符也会是一个换行符。开头和结尾的不间断的空白序列将会完全被移除。
六. global指令
1. 语法
<#global name1=value1 name2=value2 ... nameN=valueN>
或
<#global name>
capture this
</#global>
1.1. name: 变量的名称。它不是表达式。但它可以被写作是字符串形式, 如果变量名包含保留字符这是很有用的, 比如: <#global "foo-bar" = 1 />。
1.2. value: 存储的值, 是表达式。
2. 该指令和assign相似, 但是被创建的变量在所有的命名空间中都可见, 但又不会存在于任何一个命名空间之中。因此, 这个变量是全局的。
3. 如果在数据模型中, 一个相同名称的变量存在的话, 它会被使用该指令创建的变量隐藏(数据模型中的数据隐藏)。
4. 如果在当前的命名空间中, 一个相同名称的变量存在的话, 那么会隐藏由global指令创建的变量。
5. 如果全局变量被当前命名空间中一个相同名称的变量隐藏, 你可以使用 特殊变量globals, 比如: ${.globals.x}。
七. local指令
1. 语法
<#local name1=value1 name2=value2 ... nameN=valueN>
或
<#local name>
capture this
</#local>
1.1. name: 局部变量的名称。它不是一个表达式。但它可以被写作是字符串形式, 如果变量名包含保留字符, 这是很有用的, 比如: <#local "foo-bar" = 1 />。
1.2. value: 存储的值, 是表达式。
2. 它和assign指令类似, 但是它创建或替换局部变量。这仅仅在宏和方法的内部定义才会有作用。
八. function, return指令
1. 语法
<#function name param1 param2 ... paramN>
...
<#return returnValue>
...
</#function>
或
<#function name param1 param2 paramN...>
...
<#return returnValue>
...
</#function>
1.1. name: 方法变量的名称(不是表达式)
1.2. param1 param2 ... paramN: 局部变量的名称, 存储参数的值(不是表达式), 在=号后面和默认值(是表达式)是可选的。
1.3. paramN...: 最后一个参数, 可以可选的包含一个尾部省略(...), 这就意味着方法接受可变的参数数量。局部变量paramN将是额外参数的序列。
1.4. returnValue: 计算方法调用值的表达式。
1.5. return指令可以在<#function ...>和</#function>之间被用在任意位置和任意次数。
1.6. 没有默认值的参数必须在有默认值参数(paramName=defaultValue)之前。
2. 创建一个方法变量(在当前命名空间中, 如果你知道命名空间的特性)。这个指令和macro指令的工作方式一样, 除了return指令必须有一个参数来指定方法的返回值, 而且视图写入输出的将会被忽略。
九. 例子
1. 新建一个名为FMDirective的动态Web工程, 同时添加相关jar包。
2. 编写FMFactory.java
package com.fm.util;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import freemarker.template.Configuration;
import freemarker.template.TemplateExceptionHandler;
public class FMFactory {
private final static FMFactory instance = new FMFactory();
private FMFactory() {}
public static FMFactory getInstance() {
return instance;
}
private Map<String, Configuration> map = new ConcurrentHashMap<String, Configuration>();
// 创建单个Configuration实例
public synchronized Configuration getCfg(Object servletContext, String path) {
if(null != map.get(path)) {
return map.get(path);
}
Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
cfg.setServletContextForTemplateLoading(servletContext, path);
cfg.setDefaultEncoding("utf-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
map.put(path, cfg);
return cfg;
}
}
3. 编写Directive.java
package com.fm.action;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.fm.util.FMFactory;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
public class Directive extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Configuration cfg = FMFactory.getInstance().getCfg(req.getServletContext(), "/WEB-INF/templates");
Template template = cfg.getTemplate("directive.html");
Map<String, Object> root = new HashMap<String, Object>();
root.put("name", "张三");
Writer out = new OutputStreamWriter(resp.getOutputStream());
try {
template.process(root, out);
cfg.getTemplate("ws.ftl").process(root, new OutputStreamWriter(System.out));
} catch (TemplateException e) {
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
4. 修改web.xml
5. 在/WEB-INF/templates目录下编写directive.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>指令</title>
</head>
<body>
<h2>assign指令</h2>
<#import "/lib/copyright.html" as cr>
<#assign dateStart = "2000" in cr>
${cr.dateStart}<br /><br />
<#assign names>
<#list ["张三", "李四", "王五"] as name>
${name}<br />
</#list>
</#assign>
${names}
<h2>import指令</h2>
<#import "/lib/copyright.html" as cpyrgt>
<#import "/lib/copyright.html" as copyright>
${cr.dateEnd}, ${cpyrgt.dateEnd}, ${copyright.dateEnd}<br />
<#assign dateEnd="2080" in cr>
${cr.dateEnd}, ${cpyrgt.dateEnd}, ${copyright.dateEnd}
<h2>macro, nested, return指令</h2>
<h4>没有参数的宏:</h4>
<@test/>
<#macro test>
Test text
</#macro>
<h4>有参数的宏:</h4>
<#macro paramTest foo bar baaz>
ParamTest paramTest, and the params: ${foo}, ${bar}, ${baaz}
</#macro>
<@paramTest foo="a" bar="b" baaz=5*5-2/>
<h4>有参数和默认值参数的宏:</h4>
<#macro paramDefaultValueTest foo bar="Bar" baaz=-1>
ParamDefaultValueTest paramDefaultValueTest, and the params: ${foo}, ${bar}, ${baaz}
</#macro>
<@paramDefaultValueTest foo="a" bar="b" baaz=5*5-2/><br />
<@paramDefaultValueTest foo="a" bar="b"/><br />
<@paramDefaultValueTest foo="a" baaz=5*5-2/><br />
<@paramDefaultValueTest foo="a"/>
<h4>可变参数和命名参数的宏:</h4>
<#macro img src extra...>
<img src="${src?html}"
<#list extra?keys as attr>
${attr}="${extra[attr]?html}"
</#list>
>
</#macro>
<@img src="images/01.png" width=113 height=113 alt="01.png"/>
<h4>可变参数的宏, 不管是否使用命名:</h4>
<#macro m a b ext...>
a = ${a}<br />
b = ${b}<br />
<#if ext?is_sequence>
<#list ext as e>
${e?index} = ${e}<br />
</#list>
<#else>
<#list ext?keys as k>
${k} = ${ext[k]}<br />
</#list>
</#if>
</#macro>
<@m 1 2 3 4 5 /><br />
<@m a=1 b=2 c=3 d=4 e=5 data\-foo=6 myns\:bar=7 />
<h4>嵌套内容没有参数:</h4>
<#macro do_twice>
<#nested><br />
<#nested>
</#macro>
<@do_twice>something</@do_twice>
<h4>嵌套内容使用参数:</h4>
<#macro repeat count>
<#list 1..count as x>
<#nested x, x/2, x==count><br />
</#list>
</#macro>
<@repeat count=4 ; c, halfc, last>
${c}. ${halfc}<#if last> Last!</#if>
</@repeat>
<h2>return指令</h2>
<#macro testReturn>
TestReturn testReturn
<#return>
Will not be printed.
</#macro>
<@testReturn/>
</body>
</html>
6. 在/WEB-INF/templates/lib目录下编写copyright.html
<#assign dateEnd = "2050" />
7. 在/WEB-INF/templates目录下编写ws.ftl
<#ftl encoding="utf-8" strip_whitespace=true strip_text=false />
默认输出:
[ t(整体削减): 忽略本行中首和尾的所有空白。
]
[ lt(左侧削减): 忽略本行中首部所有的空白。
]
[ rt(右侧削减): 忽略本行中尾部所有的空白。
]
t, lt, rt指令:
[ t(整体削减): 忽略本行中首和尾的所有空白。<#t />
]
[ lt(左侧削减): 忽略本行中首部所有的空白。<#lt />
]
[ rt(右侧削减): 忽略本行中尾部所有的空白。<#rt />
]
默认输出:
[
我
我 我
我我我的心好痛。
]
compress指令:
[<#compress>
我
我 我
我我我的心好痛。
</#compress>]
全局变量:
<#assign name="王五" />
<#global name="李四" />
${name}
${.globals.name}
<#global saveCode>
全局变量储存中间的代码片段。
</#global>
${saveCode}
方法指令:
<#function avg x y>
<#local z = 20 />
<#return (x + y + z) / 2 />
</#function>
${avg(10, 20)}
8. 运行项目, 浏览器输出
9. 运行项目, 控制台输出