*关于httl开源Java模板的使用心得
1.简介
HTTL (Hyper-Text Template Language) 是一个高性能的开源JAVA模板引擎, 适用于动态HTML页面输出, 可替代JSP页面, 指令和Velocity相似。
2.模板语法
HTTL语法尽可能符合HTML和JAVA开发者的直觉,指令和老牌的Velocity类似,但改进了Velocity中不符合直觉的地方。 只保留最基本的条件迭代控制指令,渲染过程不允许修改原始数据,防止模板引入过多业务逻辑。默认使用HTML注释语法,避免干扰原生HTML页面。
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
 | 
<!--#set(List<Book> books)--><html>    <body>        <!--#if(books)-->        <table>            <!--#for(Book book : books)-->            <tr>                <td>${book.title}</td>            </tr>            <!--#end-->        </table>        <!--#end-->    </body></html> | 
3.改进Velocity不符合直觉的地方:
- 指令中的变量不用加$符,如:#if(a == b),而不像Velocity那样:#if($a == $b),加$有点废话,而且容易忘写。
 - ${x}当变量为null时,输出空白串,而不像Velocity那样:输出源码${x},如果用$!{x},感叹号容易忘记写。
 - 支持在输出时进行表达式计算,如:${i + 1},而不像Velocity那样:要先#set($j = $i + 1)到一个临时变量。
 - 采用更直观的方式,调用静态工具方法,如:${"a".toChar},而不像Velocity那样:${StringTool.toChar("a")}。
 
4.基本语法
和Velocity类似
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
 | 
<html>    <body>        #if(books)        <table>            #for(Book book : books)            <tr>                <td>${book.title}</td>            </tr>            #end        </table>        #end    </body></html> | 
5.注释语法
指令两边可以套上HTML注释,以免干扰原生HTML页面。
HTTL在解析时,将自动去除指令边上的HTML注释符。(缺省已开启过滤)
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
 | 
<html>    <body>        <!--#if(books)-->        <table>            <!--#for(Book book : books)-->            <tr>                <td>${book.title}</td>            </tr>            <!--#end-->        </table>        <!--#end-->    </body></html> | 
缺省使用HTML注释符:(缺省值,不用配置)
| 
 1 
2 
 | 
comment.left=<!--comment.right=--> | 
如果你用HTTL生成Java代码,你也可以改为:
| 
 1 
2 
 | 
comment.left=/*comment.right=*/ | 
6.属性语法
基于Html标签属性: (指令和表达式与注释语法相同)
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
 | 
<html>    <body>        <table if="books">            <tr for="Book book : books">                <td>${book.title}</td>            </tr>        </table>    </body></html> | 
需要配置:
| 
 1 
 | 
template.filters+=httl.spi.filters.AttributeSyntaxFilter | 
属性语法需要用到jericho包解析HTML标签:
| 
 1 
2 
3 
4 
5 
 | 
<dependency>    <groupId>net.htmlparser.jericho</groupId>    <artifactId>jericho-html</artifactId>    <version>3.1</version></dependency> | 
如果属性和其它框架冲突,可以添加名称空间:
| 
 1 
 | 
attribute.namespace=httl | 
名称空间写法如:
| 
 1 
 | 
<tr httl:for="book : books" /> | 
在没有标签的地方,你可以同时使用上面的注释语法。
7.兼容语法
HTTL提供兼容Velocity语法的支持,只需配置:(从1.0.8版本开始支持)
| 
 1 
 | 
template.filters+=httl.spi.filters.VelocitySyntaxFilter | 
在兼容模式下,你依然可以使用HTTL的标准语法,便于逐步迁移。
8.指令
HTTL只有 #set, #if, #else, #for, #break, #macro 六个指令,以及输出占位和注释转义,保持最小指令集,以后也不会增加指令。
不识别的指令名,将以文本输出,比如HTML颜色:#FF00EE。
如果指令名和文本相接,如:#elseTEXT,可用无参括号隔开,如:#if(x)AAA#else()BBB#end()CCC
9.输出指令
${}过滤输出
输出表达式的计算结果,并进行过滤,比如:过滤变量中的HTML标签。
| 
 1 
2 
3 
4 
5 
 | 
格式:${expression}示例:${user.name} | 
注:HTTL缺省开启了EscapeXmlFilter,以防止HTML注入攻击,如果你需要更强的过滤,请自行实现Filter,并配置到value.filters。此处为运行时热点,请注意性能。
如果输出变量的类型为Template,则缺省不过滤,比如:${include("foo.httl")}
$!{}不过滤输出
原样输出表达式的计算结果,不进行任何过滤,通常用于输出HTML片段。
| 
 1 
2 
3 
4 
5 
 | 
格式:$!{expression}示例:$!{body} | 
注意:使用不过滤输出,请确保内容是开发者确定的安全内容,而不是由用户提交的内容,以防止HTML注入攻击。
10.变量指令
#set变量类型
声明变量的类型,模板内部其它变量类型基于此类型推导。
| 
 1 
2 
3 
4 
5 
 | 
格式:#set(type name, type name)示例:#set(User user, List<Book> books) | 
注:暂时只支持List和Map的一级泛型,多级泛型能解析,但不能推导。
如果有全局的变量,可以用配置全局导入变量类型声明,就不需要在每个模板声明:
| 
 1 
 | 
import.variables+=User loginUser | 
缺省已导入:(可以在模板中直接使用)
| 
 1 
 | 
import.variables=Context parent,Template super,Template this,Engine engine | 
如果使用的是HTTL内置的MVC集成,在集成中已缺省导入一些相关的常用变量,
如果未声明的变量,缺省为Object:(如果是直接输出变量,可不声明类型)
| 
 1 
 | 
default.variable.type=java.lang.Object | 
| 
 1 
 | 
${obj} // 直接输出,不取属性,不做运算,不需要声明类型 | 
如果你的系统中,大部分为String类型操作,你也可以改成缺省String类型:
| 
 1 
 | 
default.variable.type=java.lang.String | 
多次设置变量的类型,如果类型为父子关系,以子类型优先:
| 
 1 
2 
 | 
#set(ParentClass var1)#set(ChildClass var1) | 
不管先后顺序,都以子类型优先:
| 
 1 
2 
 | 
#set(ChildClass var1)#set(ParentClass var1) | 
如果是两个完全不同的类型,将报错:
| 
 1 
2 
 | 
#set(OriginClass var1)#set(DiffrentClass var1) | 
#set变量赋值
将表达式的计算结果存入变量中。
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
 | 
格式:#set(name = expression)#set(type name = expression)#set(name := expression)#set(type name := expression)示例:#set(price = book.price * book.discount)#set(int price = book.price * book.discount) | 
注意,为了简化模板的书写,#set的变量全模板有效,不限制在块指令内:
| 
 1 
2 
3 
4 
 | 
#if(xxx)    #set(var ="value") // 变量全模板有效,而不是if块内有效#end${var} // 可以访问到if块内var的值 | 
不需要像Java那样:
| 
 1 
2 
3 
4 
5 
 | 
#set(var = null)#if(xxx)    #set(var ="value")#end${var} | 
类型的声明,同时可以用作强制转型,比如:
| 
 1 
 | 
#set(Book book = bookentry.value) | 
如果bookentry.value的类型丢失,上面的写法可以恢复book的类型。
一个set指令可同时有多个类型声明或赋值,用逗号分隔,但类型声明和赋值要分开写,如:
| 
 1 
2 
 | 
#set(User user, List<Book> books)#set(price = book.price, discount = book.discount) | 
赋值会在生成局部变量的同时,写入当前Context中, 如果有include()子模板,在子模板中也可以读到该变量。
如果你要在父模板中拿到include子模板中的变量,或者你想在模板渲染之后拿到变量, 因模板渲染完,它的Context已pop(),所以需在模板中将变量写到上级Conetxt才能在外面读到。 如果你需要用向上级Context中赋值,可以用“:=”赋值,它将变量写入Context.getParent()中, 比如:
| 
 1 
 | 
#set(price := book.price * book.discount) | 
你可以在模板渲染后,通过Context.getContext().get("price");拿到上面的变量。
注意:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
 | 
// 你可以把入参设成不可修改的Map,不会影响运行。Map<String, Object> parameters = Collections.unmodifiableMap(parameters);// 传入的parameters在渲染过程中总是不会被修改,// 确保渲染过程无副作用,以及多次渲染的幂等性。template.render(parameters, writer);// 模板中的#set(price = x)变量是put到Context的临时集合中的。Context.getContext().put("price", x);// 先在#set临时集合中查找,再到原生传入的parameters中查找,然后到上一级Context查找。Context.getContext().get("price"); | 
11.条件指令
#if条件
如果条件表达式计算结果为真或非空,则输出指令所包含的块。
注意:对于非Boolean值:非零数字,非空字串,非空集合,非null对象,为真。
| 
 1 
2 
3 
4 
5 
6 
7 
 | 
格式:#if(expression)示例:#if(user.role =="admin")    ...#end | 
#else条件否则
如果前面的#if条件不为真,则输出#else指令所包含的块。
注意:#else指令可以直接带条件,以减少#if条件的组合。
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
 | 
格式:#else#else(expression)示例:#if(user.role =="admin")    ...#else(user.role =="member")    ...#else    ...#end | 
12.循环指令
#for循环
迭代表达式产生的集合,以集合中的每项值,重复输出指令所包含的块。
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
 | 
格式:#for(name : expression)#for(type name : expression)示例:#for(book : books)    ...    ${for.index} //当前循环次数    ${for.size} //循环集合大小    ${for.first} //是否为第一次    ${for.last} //是否为最后一次#end | 
多级#for循环,可以用${for.parent.index}或${for.parent.parent.index}获取状态。
类型的声明,同时可以用作强制转型,比如:
| 
 1 
2 
3 
 | 
#for(Book book : booklist)    ${book.title}#end | 
如果booklist的泛型丢失,上面的写法可以恢复book的类型。
在迭代前,可以对集合做操作,如:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
 | 
## 执行9次#for(9)## 字面序列集合,输出1到9的数字#for(i : 1..9)## 字面离散集合,输出10,20,30三个数字#for(i : [10, 20, 30])## 取一个非空集合迭代,如果books1不为空,则迭代books1,否则迭代books2#for(book : books1 || books2)## 集合相加后,再迭代#for(book : books1 + books2)## 集合排序后,再迭代#for(book : books.sort)## 递归迭代,比如Menu有一个getChildren()方法返回子列表:#for(Menu menu : menus.recursive("getChildren")) | 
#break循环中断
当条件表达式为真或非空时,中断当前迭代过程。
注意:#break可以直接带条件参数:#break(i == j),不用写成:#if(i == j) #break #end
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
 | 
格式:#break#break(expression)示例:#for(book : books)    ...    #break(for.index == 10)    ...#end | 
#else循环否则
如果前面的#for集合为空,则输出#else指令所包含的块。
注意:#for指令可以直接和#else联合使用,可以减少#if不为空判断条件的书写。
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
 | 
格式:#else#else(expression)示例:#for(book : books)    ...#else    ...#end | 
13.模板指令
#macro模板片段
将指令块封装成可复用的模板片段,它可当作变量传递,可重复执行输出,可被继承覆盖。
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
 | 
格式:#macro(name)#macro(name(name, name))#macro(name(type name, type name))示例:#macro(xxx)    ...#end${xxx} 以变量执行宏${xxx(arg1, arg2)} 以方法执行宏 | 
模板继承宏覆盖,参见:继承示例
在宏定义的同时,可以在定义的位置同时输出宏,或将宏赋值给变量,以及定义宏的参数,以简化书写。
| 
 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 
 | 
格式:#macro($name)#macro($name(name, name))#macro($name(type name, type name))#macro(var = name)#macro(var = name(name, name))#macro(var = name(type name, type name))#macro($name => cache)#macro($name(name, name) => cache)#macro($name(type name, type name) => cache)#macro(var = name => cache)#macro(var = name(name, name) => cache)#macro(var = name(type name, type name) => cache)示例:#macro($xxx)    ...#end#macro(xxx = xxx)    ...#end | 
组合语法等价关系:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
 | 
#macro($xxx)    ...#end等价于:(在宏定义的位置同时输出)#macro(xxx)    ...#end${xxx} | 
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
 | 
#macro(aaa = xxx)    ...#end等价于:(在宏定义的同时执行,并将结果赋值给指定变量)#macro(xxx)    ...#end#set(aaa = xxx) | 
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
 | 
#macro($xxx => cache)    ...#end等价于:(在宏定义的位置同时输出)#macro(xxx)    ...#end${cache(xxx)} | 
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
 | 
#macro(aaa = xxx => cache)    ...#end等价于:(在宏定义的同时执行,并将结果赋值给指定变量)#macro(xxx)    ...#end#set(aaa = cache(xxx)) | 
#break模板中断
当条件表达式为真或非空时,中断当前模板或宏的执行。
注意:不在#for指令中的#break,表示中断模板或宏的执行。
| 
 1 
2 
3 
4 
5 
6 
 | 
格式:#break#break(expression)示例:#break(debug) | 
14.注释指令
##行注释
隐藏行注释的内容,以换行符结束,用于注解过程,或屏蔽指令内容。
| 
 1 
2 
3 
4 
5 
 | 
格式:## line comment示例:## This is line comment | 
#**#块注释
隐藏块注释内容,可包含换行符,用于注解过程,或屏蔽指令内容。
| 
 1 
2 
3 
4 
5 
6 
7 
 | 
格式:#* block comment *#示例:#*    This is block comment*# | 
15.转义指令
#[]#不解析块
原样输出模板内容,用于输出纯文本内容,或批量转义块中的特殊符。
| 
 1 
2 
3 
4 
5 
 | 
格式:#[ no parse block ]#示例:#[ This is no parse block: #if ${name} ]# | 
\#\$特殊符转义
原样输出指令特殊符,用于输出纯文本内容。
| 
 1 
2 
3 
4 
5 
6 
7 
 | 
格式:\#, \$, \\示例:\#xxx\${xxx}\\${xxx} | 
16.表达式
基于Java表达式和扩展方法。
支持Java所有表达式,以下只列出与Java不同的点:
- 所有null值的操作均返回null,比如:${foo.bar.blabla},如果foo为null,后面所有的操作最终为null,而不会空指针。
 - 双等号"=="会被解析成equals()方法比较,而不是比内存地址。
 - 单双引号都将生成字符串:'a'或"a"都是String类型,如果要字面声明char,请使用反单引号:`a`为char类型。
 - 加号"+"数字优先,${1 +"2"}输出3,而不是12,字符串拼接尽量用${s1}${s2},而不是${s1 + s2}
 - Bean属性会解析成getter方法调用,${user.name}等价于${user.getName()}
 - 所有实现Comparable的对象都支持比较运算符,比如:#if(date1 < date2),可以比较日期的先后。
 - 所有对象都支持逻辑与或,分别返回空值或非空值,比如:${list1 || list2},如果list1不为空则返回list1,否则返回list2。
 - List和Map可以方括号取值,比如:list[0]等价于list.get(0),map["abc"]等价于map.get("abc")
 - 大写3L将生成java.lang.Long值,小写3l将生成long值。
 - 支持is操作符,与instanceof相同,来源C#。
 
属性查找顺序,以${obj.foo}为例:(编译时决定,不影响性能)
- 首先查找有没有导入obj类型的foo()静态方法
 - 再查找obj.getFoo()函数
 - 再查找obj.isFoo()函数
 - 再查找obj.foo()函数
 - 再查找obj.foo属性
 
17.操作符表达式
集合操作符
| 
 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 
 | 
${list[0]} 等价于:${list.get(0)}${map.abc} 等价于:${map.get("abc")}${map["a.b.c"]} 等价于:${map.get("a.b.c")}序列生成: 1..3比如:#for(i : 1..10)${i}#endList生成: [123,"abc", var]比如:#for(color : ["red","yellow","blue"])${color}#endMap生成: ["xxx": 123,"yyy":"abc","zzz": var]比如:(此Map保持声明时的顺序)#for(entry : ["red":"#FF0000","yellow":"#00FF00"])${entry.key} = ${entry.value}#end集合相加:list1 + list2比如:#for(item : list1 + list2)${item}#end | 
逻辑操作符
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
 | 
#if(object)等价于:#if(object != null)#if(string)等价于:#if(string!= null && string.length() > 0)#if(list)等价于:#if(list != null && list1.size() > 0)#for(item : list1 || list2)等价于:#for(item : list1 != null && list1.size() > 0 ? list1 : list2) | 
日期操作符
| 
 1 
2 
3 
4 
 | 
date1 > date2date1 >= date2date1 < date2date1 <= date2 | 
18.函数表达式
转型函数
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
 | 
obj.to("com.foo.Bar")obj.toMapnum.toDatestr.toDatestr.toDate("yyyy-MM-dd HH:mm:ss")str.toCharstr.toBooleanstr.toBytestr.toIntstr.toLongstr.toFloatstr.toDoublestr.toClassstr.toLocale | 
集合函数
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
 | 
数组和List一样可以用size方法获取大小array.sizelist.sizemap.sizelist.sort#for(item : list.sort)#endlist.toCycle#set(colors = ["red","blue","green"].toCycle)#for(item : list)${colors.next}#end | 
文件函数
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
 | 
继承模板,以当前模板中的宏,替换父模板中的同名宏,执行父模板,输出到当前位置。${extends("/layout.httl")}${extends("/layout.httl","UTF-8")}${extends("../layout.httl")}${extends("../layout.httl","UTF-8")}包含模板,执行目标模板,输出到当前位置。${include("/template.httl")}${include("/template.httl","UTF-8")}${include("/template.httl", ["arg":"value"])}${include("../template.httl")}${include("../template.httl","UTF-8")}包含模板中的宏,执行目标宏,输出到当前位置。${include("/template.httl#macro")}${include("/template.httl#macro","UTF-8")}${include("/template.httl#macro")}${include("/template.httl#macro", ["arg":"value"])}读取目标文件中的内容,输出到当前位置。${read("/text.txt")}${read("/text.txt","UTF-8")} | 
国际化函数
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
 | 
${"key".message} 或 ${message("key")}在localized=true时,依次查找下面文件中的key配置:basename_zh_CN.propertiesbasename_zh.propertiesbasename.properties${include("template.httl")}在localized=true时,依次查找以下文件是否存在:template_zh_CN.httltemplate_zh.httltemplate.httl | 
httl.properties相关配置:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
 | 
# 国际化信息配置文件前缀,将从Loader中查找/WEB-INF/messages_zh_CN.propertiesmessage.basename=/WEB-INF/messages# 国际化信息格式,支持message和string# 分别对应MessageFormat.format()和String.format()message.format=message# 用户可以直接用UTF-8文件保存国际化信息,而不需要ascii2nativemessage.encoding=UTF-8# 开启国际化查找localized=true# 缺省区域信息locale=zh_CN | 
格式化函数
| 
 1 
2 
3 
4 
 | 
num.format("###,##0")num.format("###,##0.##")date.format("yyyy-MM-dd")date.format("yyyy-MM-dd HH:mm:ss") | 
转义函数
| 
 1 
2 
3 
4 
5 
6 
7 
8 
 | 
str.escapeXmlstr.unescapeXmlstr.escapeUrlstr.unescapeUrlstr.escapeStringstr.unescapeStringstr.escapeBase64str.unescapeBase64 | 
JSON函数
| 
 1 
2 
3 
4 
5 
6 
7 
8 
 | 
# 将对象转成JSON串obj.encodeJson# 将JSON串解析成Map对象str.decodeJson.toMap# 将JSON串解析成对象str.decodeJson("com.foo.Bar").to("com.foo.Bar") | 
缺省使用内置的解析器转码JSON串。
如需使用fastjson进行转码,需配置:
httl.properties:
| 
 1 
 | 
json.codec=httl.spi.codecs.FastjsonCodec | 
pom.xml:
| 
 1 
2 
3 
4 
5 
 | 
<dependency>    <groupId>com.alibaba</groupId>    <artifactId>fastjson</artifactId>    <version>1.1.25</version></dependency> | 
XML函数
| 
 1 
2 
3 
4 
5 
 | 
# 将对象转成XML串obj.encodeXml# 将XML串解析成对象str.decodeXml.to("com.foo.Bar") | 
缺省使用java.beans.XMLEncoder进行转码。
如需使用xstream进行转码,需配置:
httl.properties:
| 
 1 
 | 
xml.codec=httl.spi.codecs.XstreamCodec | 
pom.xml:
| 
 1 
2 
3 
4 
5 
 | 
<dependency>    <groupId>com.thoughtworks.xstream</groupId>    <artifactId>xstream</artifactId>    <version>1.4.3</version></dependency> | 
摘要函数
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
 | 
# 生成MD5码str.md5# 生成SHA码str.sha# 生成指定类型的摘要码str.digest("MD5")str.digest("SHA") | 
命名转换函数
| 
 1 
2 
3 
4 
5 
6 
7 
8 
 | 
# 转成下划线分隔命名str.toUnderlineName# 转成驼峰分隔命名 (首字母小写)str.toCamelName# 转成大写分隔命名 (首字母大写)str.toCapitalName | 
系统函数
| 
 1 
2 
3 
4 
5 
6 
7 
8 
 | 
# 当前时间${now()}# 随机数${random()}# 唯一码uuid() | 
20.Velocity对比
如果你用过Velocity模板,可以查看以下对比,加深了解:
<1>语法对比
HTTL指令中的变量不加$符,只支持#if(x == y),不支持#if($x == $y),因为指令中没有加引号的字符串就是变量,和常规语言的语法一样,加$有点废话,而且容易忘写。
HTTL占位符必需加大括号,只支持${aaa},不支持$aaa,因为$在JavaScript中也是合法变量名符号,而${}不是,减少混淆,也防止多人开发时,有人加大括号,有人不加,干脆没得选,都加,保持一致。
HTTL占位符当变量为null时输出空白串,而不像Velocity那样原样输出指令原文,即${aaa},等价于Velocity的$!{aaa},以免开发人员忘写感叹号,泄漏表达式源码,如需原样输出,可使用转义\${aaa}, 在HTTL中,${aaa}表示不对内容进行过滤,用于原样输出HTML片段。
HTTL支持在所有使用变量的地方,进行表达式计算,也就是你不需要像Velocity那样,先#set($j = $i + 1)到一个临时变量,再输出临时变量${j},HTTL可以直接输出${i + 1},其它指令也一样,比如:#if(i + 1 == n)。
HTTL采用扩展Class原生方法的方式,如:${"a".toChar},而不像Velocity的Tool工具方法那样:$(StringTool.toChar("a")),这样的调用方式更直观,更符合代码书写习惯。
<2>指令对比
| Velocity | HTTL | 异同 | 功能 | 变化 | 
| ${xxx.yyy} $xxx.yyy  | 
${xxx.yyy} | 相同 | 输出占位符 | HTTL大括号必需 | 
| $!{xxx.yyy} $!xxx.yyy  | 
$!{xxx.yyy} | 不同 | 空值不显示源码 | VM为空值不显示源码 HTTL改为不过滤输出  | 
| ## ... #* ... *#  | 
## ... #* ... *#  | 
相同 | 不显示注释块 | |
| #[[ ... ]]# | #[ ... ]# | 相似 | 不解析文本块 | HTTL少一对方括号 | 
| \# \$ \\ | \# \$ \\ | 相同 | 特殊符转义 | |
| #set($xxx = $yyy) | #set(xxx = yyy) #set(Type xxx = yyy) #set(Type xxx)  | 
相同 | 给变量赋值 | HTTL可带类型声明 | 
| #if($xxx == $yyy) | #if(xxx == yyy) | 相同 | 条件判断 | |
| #elseif($xxx == $yyy) | #else(xxx == yyy) | 相似 | 否则条件判断 | HTTL复用#else指令 | 
| #else | #else | 相同 | 否则判断 | |
| #end | #end #endif #end(if)  | 
相同 | 结束指令 | HTTL可带配对指令名 | 
| #foreach($item in $list) | #for(item : list) #for(Type item : list)  | 
相似 | 列表循环 | HTTL改为Java格式 | 
| #break | #break #break(xxx == yyy)  | 
相同 | 中断循环 | HTTL可以直接带条件 | 
| #stop | #break #break(xxx == yyy)  | 
相似 | 停止模板解析 | HTTL复用#break指令 | 
| #macro($xxx) | #macro(xxx) | 不同 | 可复用模板片段宏 | VM将宏作为指令执行 HTTL作为函数执行  | 
| #define($xxx) | #macro(xxx = xxxmacro) | 相似 | 捕获块输出到变量中 | HTTL复用#macro指令 | 
| #include("xxx.txt") | ${read("xxx.txt")} | 相似 | 读取文本文件内容 | HTTL改为函数扩展 | 
| #parse("xxx.vm") | ${include("xxx.httl")} | 相似 | 包含另一模板输出 | HTTL改为函数扩展 | 
| #evaluate("${1 + 2}") | ${render("${1 + 2}")} | 相似 | 模板求值 | HTTL改为函数扩展 | 
*关于httl开源Java模板的使用心得的更多相关文章
- httl开源JAVA模板引擎,动态HTML页面输出
		
HTTL(Hyper-Text Template Language)是一个适用于HTML输出的开源JAVA模板引擎,适用于动态HTML页面输出,可用于替代JSP页面,它的指令类似于Velocity. ...
 - Java模板引擎 HTTL
		
新一代java模板引擎典范 Beetl http://www.oschina.net/p/httl HTTL(Hyper-Text Template Language)是一个高性能的开源JAVA模板引 ...
 - Thymeleaf(Java模板引擎)
		
一.概念 1.Thymeleaf是Web和独立环境的开源的Java模板引擎,能够处理HTML,XML,JavaScript,CSS甚至纯文本:2.Thymeleaf可以在Web(基于Servlet)和 ...
 - 阅读优秀的JAVA模板引擎Beetl的使用说明有感
		
由于项目需要,对包括Beetl在内的JAVA模板引擎技术进行了学习 Beetl是由国人李家智(昵称闲大赋)开发的一款高性能JAVA模板引擎,对标产品是Freemaker 感慨于近几年国内开源项目的蓬勃 ...
 - jetbrick,新一代 Java 模板引擎,具有高性能和高扩展性
		
新一代 Java 模板引擎,具有高性能和高扩展性. <!-- Jetbrick Template Engineer --> <dependency> <groupId&g ...
 - Java 模板引擎 jetbrick-template
		
jetbrick-template 是一个新一代 Java 模板引擎,具有高性能和高扩展性. 适合于动态 HTML 页面输出或者代码生成,可替代 JSP 页面或者 Velocity 等模板. 指令和 ...
 - [置顶] 提高生产力:开源Java工具包Jodd(Java的”瑞士军刀”)
		
官方网站:http://jodd.org/ 下载地址:http://jodd.org/download/index.html Jodd=tools + ioc + mvc + db + aop + t ...
 - WEKA,一个开源java的数据挖掘工具
		
开始研究WEKA,一个开源java的数据挖掘工具. HS沉寂这么多天,谁知道偏偏在我申请离职的时候给我安排了个任务,哎,无语. 于是,今天看了一天的Weka. 主要是看了HS提供的三个文章(E文,在g ...
 - Nutch 是一个开源Java 实现的搜索引擎
		
Nutch 是一个开源Java 实现的搜索引擎.它提供了我们运行自己的搜索引擎所需的全部工具.包括全文搜索和Web爬虫. Nutch的创始人是Doug Cutting,他同时也是Lucene.Hado ...
 
随机推荐
- XAF进修二:在XAF中打开自定义的WinForm
			
在建造WinForm时须要加上一机关函数和Show办法 using System; using System.Collections.Generic; using System.ComponentMo ...
 - 嗯,开通blog了!
			
应老师建议,开通博客,“把学习时遇到的疑惑和问题随时用blog记录下来”,“把前期的学习心得写上,有时间最好把自己的学习计划也写上”. 用博客记录自己Linux和其他技术的学习日记,记录下学习实践中遇 ...
 - jQuery Mobile 表单基础
			
jQuery Mobile 会自动为 HTML 表单添加优异的便于触控的外观. jQuery Mobile 表单结构 jQuery Mobile 使用 CSS 来设置 HTML 表单元素的样式,以使其 ...
 - Hibernate一对多(注解)
			
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hi ...
 - 移动APP开发使用什么样的原型设计工具比较合适?
			
原型设计工具有Axure,Balsamiq Mockups,JustinMind,iClap原型工具,等其他原型工具.其中JustinMind比较适合APP开发使用. JustinMind可以输出Ht ...
 - MySQL 性能优化的最佳20多条经验分享
			
当我们去设计数据库表结构,对操作数据库时(尤其是查表时的SQL语句),我们都需要注意数据操作的性能.这里,我们不会讲过多的SQL语句的优化,而只是针对MySQL这一Web应用最多的数据库.希望下面的这 ...
 - [翻译][erlang]cowboy handler模块的使用
			
关于Cowboy Cowboy是基于Erlang实现的一个轻量级.快速.模块化的http web服务器. Handlers,用于处理HTTP请求的程序处理模块. Plain HTTP Handlers ...
 - [Shell] swoole_timer_tick 与 crontab 实现定时任务和监控
			
手动完成 "任务" 和 "监控" 主要有下面三步: 1. mission_cron.php(定时自动任务脚本): <?php /** * 自动任务 定时器 ...
 - RFC2119:表示要求的动词(转)
			
RFC(Request For Comments)指的关于互联网标准的正式文件,它们的内容必须写得非常清楚. 表达的时候,必须严格区分哪些是"建议"(suggestion),哪些是 ...
 - 设置secureCRT的鼠标右键为弹出文本操作菜单功能
			
options菜单下的 global options 页面的 terminal 中的 mouse 子菜单对 paste on right button 的选项取消勾选即可.