这个是 jetbrick-template
模板语法参考手册。我们推荐的模板文件扩展名为 .jetx
,嵌入式子模板的扩展名为 .inc.jetx
。
§值 Value
语法:
${expression}
:输出表达式的计算结果。$!{expression}
:输出表达式的计算结果,并 escape 其中的 HTML 标签。
其中 expression
为任何合法的 Java 表达式,参考: 表达式。
示例:
${user.name}
${user.book.available()}
$!{user.description}
注意:
- 如果
expression
为null
,则不会输出任何东西,如果需要输出null
,可以使用如下的方法扩展:${expression.asString()}
。- 如果
expression
的返回类型为void
,那么也不会做任何输出动作。
§指令 Directive
§1. 变量类型声明 #define
jetbrick-template 为了提高性能,采用了强类型来编译模板,所以需要为每一个用到的变量定义变量类型。如下:
语法:
#define(type name, ...)
示例:
#define(String name)
#define(UserInfo user, List<UserInfo> userlist)
注意:
- 在相同作用域下面,不允许重复定义变量类型,变量只在当前作用域有效。
- 对于没有定义变量类型,默认作用域为全局有效(Global)。
- 对于没有定义变量类型,那么优先从上下文表达式中进行类型推导, 否则默认类型为
Object
。
§2. 赋值语句 #set
语法:
#set(type name = value, ...)
#set(name = value, ...)
示例:
#set(int num = 1+2*3)
#set(color1 = "#ff0000", color2 = "#00ff00")
注意:
- 在相同作用域下面,不允许重复定义变量类型,变量只在当前作用域有效。
- 影响当前模板,以及子模板的
JetContext
变量。- 不影响父模板的
JetContext
变量。
§3. 赋值语句 #put
将变量内容保存到当前模板以及所有父模板的 JetContext
中,方便父子模板间进行变量传递。
语法:
#put(name, value)
#put(name1, value1, name2, value2, ...)
示例:
#put("num", 1 + 2 * 3)
#put("user", user, "name", "jetbrick")
注意:
- 可以传递多个 key-value 对
- 参数
name
必须是String
类型
§4. 条件语句 #if, #elseif, #else
如果 #if
条件表达式计算结果为 true
或非空,则输出指令所包含的块, 否则输出 #else
指令块。
语法:
#if(expression)
...#end
#if(expression)
...#else
...#end
#if(expression)
...#elseif(expression)
...#else
...#end
示例:
#if (user.role == "admin")
...
#elseif (user.role == "vip")
...
#elseif (user.role == "guest")
...
#else
...
#end
注意:
- 对于
expression
为非Boolean
值:非零数字,非空字串,非空集合,非null
对象,即为true
。#elseif
允许出现多次。- 如果
#else
后面紧跟着其他文本,比如#elseABC
,那么可以通过添加一对空括号来分割,修改成#else()ABC
。这样可读性就能加强,模板解析也不会出现问题。所有的无参数指令,比如#end
,#break
,#stop
都支持这样操作。()
前面和之间请不要插入任何空格。
§5. 循环语句 #for
循环重复输出指令所包含的块,如果是空的集合对象,那么输出 #else
块。
语法:
#for(id: expression)
...#end
#for(id: expression)
...#else
...#end
#for(type id: expression)
...#end
#for(type id: expression)
...#else
...#end
#for
支持以下类型的 expression
- Iterator
- Iterable (Collection, ...)
- Map
- Enumeration
- Array
- null (空循环)
- Object (只循环一次)
示例:
#for (book : user.books)
${for.index} // 内部循环计数器,从 1 开始计数
...
#end
循环变量 id 类型声明,用作强制转型,避免类型推导失败。
#for (Book book : user.books)
...
#end
指令 #else
可用于循环为空时的内容输出。
#for (Book book : user.books)
...
#else
No books are found in here.
#end
§5.1 for 内部对象
for.index
可用于内部循环计数,从 1 开始计数。for.size
获取循环总数。如果对 Iterator 进行循环,或者对非 Collection 的 Iterable 进行循环,则返回 -1。for.first
是否第一个元素。for.last
是否最后一个元素。for.odd
是否第奇数个元素。for.even
是否第偶数个元素。
§6. 循环中断或继续语句 #break, #continue
当 expression
为 true
,#break
中断当前循环,而 #continue
跳过余下的内容,跳到下一个循环。
语法:
#break
#break(expression)
#continue
#continue(expression)
示例:
#for (book : user.books)
...
#break(book.price > 100)
...
#end
#for (book : user.books)
...
#continue(book.price > 100)
...
#end
注意:
- 无参数格式代表
expression
永远为true
。
§7. 停止解析语句 #stop
当 expression
为真或非空时,停止模板运行。
语法:
#stop
#stop(expression)
示例:
#stop(error != null)
注意:
- 无参数格式代表
expression
永远为true
。
§8. 嵌套模板语句 #include
嵌入一个子模板,将子模板内容输出到当前位置。
语法:
#include(file)
#include(file, parameters)
示例:
#include("/include/header.jetx") // 绝对路径
#include("../userinfo.jetx") // 相对路径
#include(file) // 动态路径
#include("/include/header.jetx", {role: "admin"}) // 传递参数
注意:
- 子模板自动共享父模板
JetContext
变量,同时还可以另外传递一些参数给子模板。- 子模板可以用
#put
指令修改父模板的JetContext
,然后在父模板中用context["name"]
获取变量值。
具体用法请查考: 在 jetbrick-template 中如何使用 #include?
§9. 宏定义 #macro
定义一个代码片段,然后可以重复使用。(New from 1.1.0)
语法:
#macro name(type name, ...)
...#end
注意: 每个宏可以定义 0~N 个参数。
示例:
#macro header(String name)
Hello ${name}!
#end
${header("张三")}
${header("李四")}
输出结果:
Hello 张三!
Hello 李四!
注意:
- 宏的调用就和函数调用一样。(如果和函数存在名称冲突,那么宏定义优先)
- 宏的调用返回值是 Void,所以不支持对返回值进行计算。
§10. 自定义标签 #tag
jetbrick-template 支持自定义 Tag,类似于 JSP Taglib,但是要比 JSP Taglib 更简单更好用。(New from 1.1.0)
语法:
#tag name(args ...)
...#end
注意:
- 需要在 Java 端先定义 Tag 标签的实现。
- Tag 调用的参数必须和定义的一致。
示例:
#tag layout("layout.jetx")
Hello ${name}!
#end
具体用法请查考: 在 jetbrick-template 中如何自定义 Tag?
§文本 Text
普通文本内容将会被直接进行输出。
§11. 不解析文本块
原样输出模板内容,用于输出纯文本内容,或批量转义块中的特殊字符。类似于 XML 中的 CDATA。
语法:
#[[
...]]#
示例:
#[[
Source code will be displayed in here.
Hello ${user.name}
]]#
§12. 特殊字符转义
原样输出指令特殊字符,转义字符由 \
进行转义。
语法:
\\
\#
\$
示例:
\#if
\${user.name}
\\${user.name}
注意:
- 如果遇到类似
#ff0000
类似于指令的内容,但又不是系统定义的指令,那么也会原样输出,并不需要进行转义。\
后面跟的字符不是#
和$
,也不需要进行转义,直接输出。
§注释 Comment
§13. 行注释
隐藏行注释的内容,以换行符结束,用于注解过程,或屏蔽指令内容。
语法:
##
...
示例:
${user.name} ## This is a line comment.
§14. 块注释
隐藏块注释内容,可包含换行符,用于注解过程,或屏蔽指令内容。
语法:
#--
...--#
示例:
#--
This is a block comment.
--#
§表达式 Expression
支持所有 Java 表达式,并对其进行了一些有用的扩展。
§14.1 与 Java 相同的地方 (运算符优先级和 Java 保存一致)
- 数字常量:
123
123L
0.01D
99.99E-10D
- 字符串常量:
"abc\r\n"
'abc\u00A0\r\n'
- BOOLEAN 常量:
true
false
- NULL 常量:
null
- 算术运行:
+
-
*
/
%
- 自增/自减:
++
--
- 位运算:
~
&
|
- 移位运算:
>>
>>>
<<
- 比较运算:
==
!=
>
>=
<
<=
- 逻辑运算:
!
&&
||
- 三元表达式:
? :
- 实例对象判断:
instanceof
- NEW 对象:
new Object(...)
- 强制类型转换:
(java.lang.String)obj
- 数组存取:
array[i]
- 字段访问:
obj.field
- 方法调用:
obj.method(...)
- 方法调用:支持可变参数方法(Varargs)和重载方法(Overload)
- 静态字段调用:
@Long.MAX_VALUE
- 静态方法调用:
@Long.valueOf("123")
- 支持数组定义:
String[]
- 支持泛型定义:
List<String>
Map<String[], List<?>>
§14.2 与 Java 不同的地方
- 双等号
==
会被解析成equals()
方法比较,而不是比内存地址。 - 单双引号都将生成字符串:
'a'
或"a"
都是String
类型。 - Bean 属性会解析成 getter 方法调用,
${user.name}
等价于${user.getName()}
- 所有实现
Comparable
的对象都支持比较运算符,比如:#if(date1 < date2)
,可以比较日期的先后。 - 所有对象都条件测试,并返回
true
或者false
。对于非Boolean
对象,所有非零数字,非空字串,非空集合,非null
对象,即为true
。 List
和Map
可以方括号取值,比如:list[0]
等价于list.get(0)
,map["abc"]
等价于map.get("abc")
。Map
和JetContext
对象支持.
访问内部的对象(属性调用),如:map.key
,context.key
。- 支持 Groovy 的
?.
安全调用,以避免NullPointerException
。 - 静态字段/方法调用,需要用
@
前缀,比如:@Long.MAX_VALUE
,如果类带包名,需要这么用:@(java.lang.Long).valueOf("123")
§15. 变量名 Variable
可以是任意合法的 Java 变量名:
- 其中
$
开头的变量为模板内部变量,不允许直接使用。 - 不允许使用 Java 关键字。
如:user
, user_name
, userName
2 个内置的特殊变量:
context
: 当前模板的JetContext
对象。for
: 用于#for
指令内部状态对象。具体参考#for
指令用法。
§16. List 常量
语法:
[item, ...]
示例:
[] // empty List
[1, 2, 3, 4, 5]
["abc", 123, new Date(), 1+2*3]
取值:
${list[index]}
${list.get(index)}
// 安全调用
${list?[index]}
${list?.get(index)}
§17. Map 常量
语法:
{key: value, ...}
示例:
{} // empty Map {"name" : "Jason", "statue" : 0}
取值:
${map.key}
${map["key"]}
${map.get("key")}
// 安全调用
${map?.key}
${map?["key"]}
${map?.get("key")}
§18. Bean 属性调用 bean.property
Bean 属性会解析成 getter 方法调用。
属性查找顺序,以 ${obj.foo}
为例:
- 查找 obj.getFoo() 方法
- 查找 obj.isFoo() 方法
- 查找 obj class 的 foo 字段
- 查找 obj.get(name) 方法 (如果是
Map
或者JetContext
)
以上查找过程会在第一次编译的时候完成,不影响性能。
支持对属性返回值的类型推导。
注意:
jetbrick-template
支持属性的安全调用,和 Groovy 相同,你可以使用?.
来代替.
,以避免出现NullPointerException
。
§19. Bean 方法调用 bean.method(...)
- 支持普通方法调用
- 支持不定长参数方法调用 Varargs
- 支持方法重载 Overload
- 支持扩展方法调用。参考:扩展方法调用
- 支持对方法返回值的类型推导
示例:
## 方法重载 Overload
${"Hello".substring(2)}
${"Hello".substring(2, 3)}
注意:
jetbrick-template
支持方法的安全调用,和 Groovy 相同,你可以使用?.
来代替.
,以避免出现NullPointerException
。
§20. 函数调用 function
jetbrick-template 还支持函数调用,如 ${now()}
, ${include("tag.jetx")}
。
具体参考:扩展函数调用
§21. 静态字段调用 @Class.Field
(New from 1.1.0)
语法:
@Class.Field
@(package.Class).Field
示例:
${@Long.MAX_VALUE}
${@(java.lang.Long).MAX_VALUE}
§22. 静态方法调用 @Class.method
(New from 1.1.0)
语法:
@Class.method(...)
@(package.Class).method(...)
示例:
${@Collections.emptyList()}
${@(java.lang.Long).valueOf("123")}
§默认的方法扩展 Methods
所有方法扩展定义在 jetbrick.template.runtime.JetMethods
§23. 基本数据类型转换 Cast
- String.asBoolean()
- String.asChar()
- String.asByte()
- String.asShort()
- String.asInt()
- String.asLong()
- String.asFloat()
- String.asDouble()
- String.asDate()
- String.asDate(String format)
- Object.asString()
§24. 集合类型转换 Cast
- Collection.asList()
- boolean[].asList()
- char[].asList()
- byte[].asList()
- short[].asList()
- int[].asList()
- long[].asList()
- float[].asList()
- doubl[].asList()
- Object[].asList()
§25. 数据格式化 Format
- Number.format()
- Number.format(String format)
- Date.format()
- Date.format(String format)
§26. 数据 Escape/Unescape
- String.escapeJava()
- String.unescapeJava()
- String.escapeJavaScript()
- String.unescapeJavaScript()
- String.escapeXml()
- String.unescapeXml()
- String.escapeUrl()
- String.escapeUrl(String encoding)
- String.unescapeUrl()
- String.unescapeUrl(String encoding)
§27. 默认值输出
- Object.asDefault(Object defaultValue)
§28. JSON 输出
- Object.asJSON()
§29. 字符串转换
- String.toUnderlineName()
- String.toCamelCase()
- String.toCapitalizeCamelCase()
- String.repeat(int count)
§30. 算术计算
- int[].sum()
- int[].avg()
- int[].max()
- int[].min()
§默认的函数扩展 Functions
所有函数扩展定义在 jetbrick.template.runtime.JetFunctions
§31. 常用函数
Date now()
获取当前时间int random()
获取一个随机数UUID uuid()
获取一个 UUID
§32. 循环计数生成器
生成一个用于循环的数组,主要用于 #for
的循环迭代。
@Deprecated from 1.2.6
Iterator<Integer> iterator(int start, int stop)
Iterator<Integer> iterator(int start, int stop, int step)
New added from 1.2.6
Iterator<Integer> loop(int start, int stop)
Iterator<Integer> loop(int start, int stop, int step)
范例:
#for (int i : iterator(1,100))
${i}
#end
§33. 嵌入子模板 include(...)
嵌入一个子模板。和 #include
指令的区别,此函数对子模板的输出进行了缓存,可以处理返回的内容,但是效率没有 #include
指令高。
String include(String relativeName)
String include(String relativeName, Map<String, Object> parameters)
§34. 嵌入纯文本文件 read(...)
String read(String relativeName)
String read(String relativeName, String encoding)
§35. 调试专用函数 debug(...)
void debug(String format, Object... args)
输出调试信息,需要配合 Slf4j 使用。 参数格式请查看 Slf4j Logger。
§36. Web 路径获取 ctxpath() / webroot()
String ctxpath()
返回相对于 web 根目录的绝对路径,如 /myappString ctxpath(String url)
将 url 转换为相对于 web 根目录的绝对路径,如 /myapp/path/fileString webroot()
返回完整的 web 站点路径,如 http://127.0.0.1:8080/myappString webroot(String url)
将 url 转换为完整的 web 站点路径,如 http://127.0.0.1:8080/myapp/path/file
§默认的自定义标签 Tags
所有 Tags 定义在 jetbrick.template.runtime.JetTags
#tag layout(String file)
...#end
应用页面布局。
参考:jetbrick-template 中如何实现 layout 功能?#tag layout(String file, Map<String, Object> parameters)
...#end
应用页面布局(支持传递传输)。
参考:jetbrick-template 中如何实现 layout 功能?#tag block(String name)
...#end
将块内容保存到变量名为 name 的 JetContext 中。
参考:jetbrick-template 中如何实现 layout 功能?#tag default_block(String name)
...#end
如果不存在指定的 JetContext 变量,那么输出 default_block 块内容,否则输出指定的 JetContext 变量。
参考:jetbrick-template 中如何实现 layout 功能?#tag application_cache(String name, long timeout)
...#end
将内存缓存到 ServletContext 中,在 timeout 秒之后自动超时。#tag session_cache(String name, long timeout)
...#end
将内存缓存到 HttpSession 中,在 timeout 秒之后自动超时。
§和 Velocity 的比较
§37. 语法差异
- jetbrick-template 指令中的变量不加
$
符,只支持#if(x == y)
,不支持#if($x == $y)
,因为指令中没有加引号的字符串就是变量,和常规语言的语法一样,加$有点废话,而且容易忘写。 - jetbrick-template 占位符必需加大括号,只支持
${foo}
,不支持$foo
,因为$
在 JavaScript 中也是合法变量名符号,而${}
不是,减少混淆,也防止多人开发时,有人加大括号,有人不加,干脆没得选,都加,保持一致。 - jetbrick-template 占位符当变量为
null
时输出空白串,而不像 Velocity 那样原样输出指令原文,即${foo}
,等价于 Velocity 的$!{foo}
,以免开发人员忘写感叹号,泄漏表达式源码,如需原样输出,可使用转义\${foo}
, 在 jetbrick-template 中,$!{foo}
表示对内容进行 HTML 过滤,用于原样输出 HTML 片段。 - jetbrick-template 支持在所有使用变量的地方,进行表达式计算,也就是你不需要像 Velocity 那样,先
#set($j = $i + 1)
到一个临时变量,再输出临时变量${j}
,jetbrick-template 可以直接输出${i + 1}
,其它指令也一样,比如:#if(i + 1 == n)
。 - jetbrick-template 采用扩展 Class 原生方法的方式,如:
${"str".toChar()}
,而不像 Velocity 的 Tool 工具方法那样:$(StringTool.toChar("a"))
,这样的调用方式更直观,更符合代码书写习惯。 - jetbrick-template 支持属性和方法的安全调用。如
${user?.name}
,${user?.hasRole("vip")}
。如果user
对象为null
,那么返回结果就是null
,不会出现烦人的NullPointerException
。 - jetbrick-template 还支持静态字段/方法调用,函数扩展,上下文相关的方法/函数扩展。
§38. 指令差异
velocity | jetbrick-template | 异同 | 功能 | 变化 |
---|---|---|---|---|
${foo.bar} $foo.bar |
${foo.bar} | 相同 | 输出占位符 | jetbrick-template 大括号必需 |
$!{foo.bar} $!foo.bar |
$!{foo.bar} | 不同 | 空值不显示源码 | velocity 为空值不显示源码 jetbrick-template 改为HTML过滤输出 |
## ... #* ... *# |
## ... #-- ... --# |
相似 | 注释 | 块注释格式不一样 |
#[[ ... ]]# | #[[ ... ]]# | 相同 | 不解析文本块 | |
\# \$ \\ | \# \$ \\ | 相同 | 特殊字符转义 | |
n/a | #define(Type foo = bar) | 新增 | 给变量声明类型 | |
#set($foo = $bar) | #set(foo = bar) #set(Type foo = bar) |
相同 | 给变量赋值 | 可带类型声明 |
n/a | #put(name, value) | 新增 | 保存变量到全局 | 支持父子模板参数的全局传递 |
#if($foo == $bar) | #if(foo == bar) | 相同 | 条件判断 | |
#elseif($foo == $bar) | #elseif(foo == bar) | 相同 | 否定条件判断 | |
#else | #else | 相同 | 否定判断 | |
#end | #end | 相同 | 结束指令 | |
#foreach($item in $list) | #for(item : list) #for(Type item : list) |
相似 | 循环指令 | 改为Java格式,可以带类型声明 |
#break | #break #break(foo == bar) |
相同 | 中断循环 | 可以直接带条件 |
n/a | #continue #continue(foo == bar) |
新增 | 继续下一个循环 | 可以直接带条件 |
#stop | #stop #stop(foo == bar) |
相同 | 停止模板解析 | 可以直接带条件 |
#macro($foo) | #macro foo(...) | 相似 | 可复用模板片段宏 | velocity 将宏作为指令执行 jetbrick-template 作为函数执行 |
n/a | #tag foo(...) | 新增 | 自定义标签 | jetbrick-template 允许自定义标签 |
#include("foo.txt") | read("foo.txt") | 相似 | 读取文本文件内容 | jetbrick-template 用扩展函数实现 |
#parse("foo.vm") | #include("foo.jetx") #include("foo.jetx", args) |
相同 | 包含另一模板输出 | jetbrick-template 支持私有参数传递 |
#evaluate("${1 + 2}") | n/a | 不同 | 模板求值 | jetbrick-tempate 暂不支持 |