• 关于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标签:

    jericho-html-3.1.jar

    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}
    #end
     
    List生成: [123,"abc", var]
    比如:
    #for(color : ["red","yellow","blue"])
    ${color}
    #end
     
    Map生成: ["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 > date2
    date1 >= date2
    date1 < date2
    date1 <= date2

    18.函数表达式

    转型函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    obj.to("com.foo.Bar")
    obj.toMap
    num.toDate
    str.toDate
    str.toDate("yyyy-MM-dd HH:mm:ss")
    str.toChar
    str.toBoolean
    str.toByte
    str.toInt
    str.toLong
    str.toFloat
    str.toDouble
    str.toClass
    str.toLocale

    集合函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    数组和List一样可以用size方法获取大小
    array.size
    list.size
    map.size
     
    list.sort
    #for(item : list.sort)
    #end
     
    list.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.properties
    basename_zh.properties
    basename.properties
     
    ${include("template.httl")}
    在localized=true时,依次查找以下文件是否存在:
    template_zh_CN.httl
    template_zh.httl
    template.httl

    httl.properties相关配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 国际化信息配置文件前缀,将从Loader中查找/WEB-INF/messages_zh_CN.properties
    message.basename=/WEB-INF/messages
     
    # 国际化信息格式,支持message和string
    # 分别对应MessageFormat.format()和String.format()
    message.format=message
     
    # 用户可以直接用UTF-8文件保存国际化信息,而不需要ascii2native
    message.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.escapeXml
    str.unescapeXml
    str.escapeUrl
    str.unescapeUrl
    str.escapeString
    str.unescapeString
    str.escapeBase64
    str.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:

    fastjson-1.1.25.jar

    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:

    xstream-1.4.3.jar

    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>语法对比

    1. HTTL指令中的变量不加$符,只支持#if(x == y),不支持#if($x == $y),因为指令中没有加引号的字符串就是变量,和常规语言的语法一样,加$有点废话,而且容易忘写。

    2. HTTL占位符必需加大括号,只支持${aaa},不支持$aaa,因为$在JavaScript中也是合法变量名符号,而${}不是,减少混淆,也防止多人开发时,有人加大括号,有人不加,干脆没得选,都加,保持一致。

    3. HTTL占位符当变量为null时输出空白串,而不像Velocity那样原样输出指令原文,即${aaa},等价于Velocity的$!{aaa},以免开发人员忘写感叹号,泄漏表达式源码,如需原样输出,可使用转义${aaa}, 在HTTL中,${aaa}表示不对内容进行过滤,用于原样输出HTML片段。

    4. HTTL支持在所有使用变量的地方,进行表达式计算,也就是你不需要像Velocity那样,先#set($j = $i + 1)到一个临时变量,再输出临时变量${j},HTTL可以直接输出${i + 1},其它指令也一样,比如:#if(i + 1 == n)。

    5. 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改为函数扩展

     

  • 相关阅读:
    看看大对象是如何爆你的内存
    Web Api 多项目文档生成之SwaggerUI
    react-native执行 npm install cl.exe找不到 的问题
    在SourceTree中使用Git submodule
    [ElasticSearch] 如何使用中文分詞ik與繁簡轉換stconvert插件
    [Activator-HelloAkka] Create our Actors
    [Activator-HelloAkka] Define our Actors
    [Activator- HelloAkka] Define our Messages
    [Scala] Currying
    [Scala] Pattern Matching(模式匹配)
  • 原文地址:https://www.cnblogs.com/tianri/p/7009252.html
Copyright © 2020-2023  润新知