Enjoy模版引擎
介绍
什么是模版引擎
模板引擎是为了解决用户界面(显示)与业务数据(内容)分离而产生的。他可以生成特定格式的文档,常用的如格式如HTML、xml以及其他格式的文本格式。
工作模式如下:
Enjoy模板引擎
Enjoy Template Engine专为 java 开发者打造,所以坚持两个核心设计理念:
一是在模板中可以直接与 java 代码通畅地交互
二是尽可能沿用 java 语法规则,将学习成本降到极致
因此,立即掌握 90% 的用法,只需要记住一句话:Enjoy 模板引擎表达式与 Java 是直接打通的。
另外只有 #if、#for、#define、#set、#include、#switch、#(…) 七个指令需要了解。
Enjoy配置
Enjoy的配置在主配置类 configEngine(…) 方法中
// 支持模板热加载,绝大多数生产环境下也建议配置成 true,
//除非是极端高性能的场景
engine.setDevMode(true);
// 添加共享模板函数
engine.addSharedFunction("_layout.html");
// 配置极速模式,性能提升 13%
Engine.setFastMode(true);
// jfinal 4.9.02 新增配置:支持中文表达式、
//中文变量名、中文方法名、中文模板函数名
Engine.setChineseExpression(true);
Enjoy指令
Enjoy Template Engine一如既往地坚持极简设计,核心只有 #if、#for、#switch、#set、#include、#define、#(…) 这七个指令,便实现了传统模板引擎几乎所有的功能。
如果官方提供的指令无法满足需求,还可以极其简单地在模板语言的层面对指令进行扩展,在com.jfinal.template.ext.directive 包下面就有五个扩展指令
注意:Enjoy 模板引擎指令的扩展是在词法分析、语法分析的层面进行扩展,与传统模板引擎的自定义标签类的扩展完全不是一个级别,前者可以极为全面和自由的利用模板引擎的基础设施,在更加基础的层面以极为简单直接的代码实现千变万化的功能。
输出指令
#(…) 输出指令 会将这些表达式的求值结果进行输出,特别注意,当表达式的值为 null 时没有任何输出,更不会报异常。
#(value)
#(object.field)
#(object.field ??)
#(a > b ? x : y)
#(seoTitle ?? "JFinal 俱乐部")
#(object.method(), null)
注意上图最后一行代码中的输出指令参数为一个逗号表达式,逗号表达式的整体求值结果为最后一个表达式的值,而输出指令对于null值不做输出,所以这行代码相当于是仅仅调用了 object.method() 方法去实现某些操作。
#if指令
if指令需要一个 cond 表达式作为参数,并且以 #end 为结尾符,当 cond 求值为 true 时,执行 if 分支之中的代码。
#if(cond)
...
#end
#if #else if 与 #else 分支块结构:
#if(c1)
...
#else if(c2)
...
#else if (c3)
...
#else
...
#end
#for指令
Enjoy Template Engine 对 for 指令进行了极为人性化的扩展,可以对任意类型数据进行迭代输出,包括支持 null 值迭代。
// 对 List、数组、Set 这类结构进行迭代
#for(x : list)
#(x.field)
#end
// 对 Map 进行迭代
#for(x : map)
#(x.key)
#(x.value)
#end
注意:当被迭代的目标为 null 时,不需要做 null 值判断,for 指令会自动跳过,不进行迭代。
#for状态
for指令还支持对其状态进行获取
#for(x : listAaa)
#(for.size) 被迭代对象的 size 值
#(for.index) 从 0 开始的下标值
#(for.count) 从 1 开始的记数值
#(for.first) 是否为第一次迭代
#(for.last) 是否为最后一次迭代
#(for.odd) 是否为奇数次迭代
#(for.even) 是否为偶数次迭代
#(for.outer) 引用上层 #for 指令状态
#(for.outer.size)
#end
#for其他
1.for指令还支持对任意类型进行迭代,此时仅仅是对该对象进行一次性迭代:
#for(x : article)
#(x.title)
#end
article只是一个普通对象,并不是集合
2. for 指令还支持 #else 分支语句,在for指令迭代次数为0时,将执行 #else 分支内部的语句
#for(blog : blogList)
#(blog.title)
#else
您还没有写过博客,点击此处<a href="/blog/add">开博</a>
#end
当blogList.size() 为0或者blogList为null值时,也即迭代次数为0时,会执行#else分支。
3.还支持更常规的for语句形式:
#for(i = 0; i < 100; i++)
#(i)
#end
注意:以上这种形式的for语句,比前面的for迭代少了for.size与for.last两个状态,只支持如下几个状态:for.index、for.count、for.first、for.odd、for.even、for.outer
#for 指令还支持 #continue、#break 指令,用法与java完全一致
#switch
#switch 指令对标 java 语言的 switch 语句。
#switch (month)
#case (1, 3, 5, 7, 8, 10, 12)
#(month) 月有 31 天
#case (2)
#(month) 月平年有28天,闰年有29天
#default
月份错误: #(month ?? "null")
#end
#case 指令参数还可以是任意表达式
#case (a, b, x + y, "abc", "123")
** #case 支持逗号分隔的多参数,从而无需引入 #break 指令,不仅减少了代码量,而且避免了忘写 #break 指令时带来的错误隐患。还有一个与 java 语法有区别的地方是 #case、#default 指令都未使用冒号字符。
**
#date #number
date指令用于格式化输出日期型数据,包括Date、Timestamp等一切继承自Date类的对象的输出
#date(account.createAt)
#date(account.createAt, "yyyy-MM-dd HH:mm:ss")
number 指令用于格式化输出数字型数据,包括 Double、Float、Integer、Long、
BigDecimal 等一切继承自Number类的对象的输出
#number(3.1415926, "#.##")
#number(0.9518, "#.##%")
#number(123456789, ",###")
#number(300000, "光速为每秒,### 公里。")
注释
JFinal Template Engine支持单行与多行注释
### 这里是单行注释
#--
这里是多行注释的第一行
这里是多行注释的第二行
--#
原样输出
原样输出是指不被解析,而仅仅当成纯文本的内容区块
#[[
#(value)
#for(x : list)
#(x.name)
#end
]]#
注意:用于注释、原样输出的三个控制字符之间不能有空格
#include
#include指令用于将外部模板内容包含进来,被包含的内容会被解析成为当前模板中的一部分进行使用
#include("hello.html")
**注意:#include 指令第一个参数必须为 String 常量,当以 ”/” 打头时将以
baseTemplatePath 为相对路径去找文件,否则将以使用 #include 指令的当前模板的路径为相对路径去找文件。
**
Enjoy表达式
与java规则基本相同的表达式
- 算术运算: ± * / % +±
- 比较运算: > >= < <= == !=
- 逻辑运算: ! && ||
- 三元表达式: ? :
- Null 值常量: null
- 字符串常量: “jfinal club”
- 布尔常量:true false
- 数字常量: 123 456F 789L 0.1D 0.2E10
- 数组存取:array[i]
- 属性取值:object.field
- 方法调用:object.method(p1, p2…, pn) (支持可变参数)
- 逗号表达式:123, 1>2, null, “abc”, 3+6 (逗号表达式的值为最后一个表达式的值)
属性访问
由于模板引擎的属性取值表达式极为常用,所以对其在用户体验上进行了符合直觉的扩展,field 表达式取值优先次序,以 user.name 为例:
- 如果 user.getName() 存在,则优先调用
- 如果 user 具有 public 修饰过的name 属性,则取 user.name 属性值(注意:jfinal 4.0 之前这条规则的优先级最低)
- 如果 user 为 Model 子类,则调用 user.get(“name”)
- 如果 user 为 Record,则调用 user.get(“name”)
- 如果 user 为 Map,则调用 user.get(“name”)
此外,还支持数组的length长度访问:array.length,与java语言一样。
方法调用
模板引擎被设计成与 java 直接打通,可以在模板中直接调用对象上的任何public方法,使用规则与java中调用方式保持一致。
#("ABCDE".substring(0, 3))
#(girl.getAge())
#(list.size())
#(map.get(key)
简单来说:模板表达式中可以直接调用对象所拥有的public方法,方法调用支持可变参数。
可选链操作符 ?.
jfinal 5.0.0 版本新增了 optional chain 操作符,使用方式如下:
# 当 article 为 null 时不对 title 进行取值,而是直接返回 null
article?.title
# 可用于方法调用
article?.getTitle()
# 可级联操作
page?.list?.size()
# 可用在方法调回之后,以下代码在 getList() 返回 null 时可避免抛出异常
page?.getList()?.size()
在对某个对象进行 field 取值或者方法调用,如果该对象可能为 null,那么可以使用该操作符,避免抛出异常。注意该操作符在对象为 null 时始终返回 null。
空合并安全取值调用操作符
Enjoy Template Engine 引入了swift与C#语言中的空合操作符,并在其基础之上进行了极为自然的扩展,该表达式符号为两个紧靠的问号:??。
seoTitle ?? “JFinal 社区”
object.field ??
object.method() ??
object.field ?? "默认值"
object.method() ?? Value
/*代码表示左侧null 属性取值为null时,整个表达式的值为后方的字符串中的值,
而??后面没有值的代码表示值为null时整个表达式为null。*/
特别注意:
?? 操作符的优先级高于数学计算运算符:+、-、*、/、%,低于单目运算符:!、++、–。强制改变优先级使用小括号即可。
单引号字符串
针对Template Engine 经常用于html的应用场景,添加了单引号字符串支持
<a href="/" class="#(menu == 'index' ? 'current' : 'normal')"
首页
</a>
相等与不等比较表达式增强
相等不等表达式 == 与 != 会对左右表达式进行left.equals(right)比较操作,所以可以对字符串进行直接比较
#if(nickName == "james")
...
#end
布尔表达式增强
布尔表达式在原有java基础之下进行了增强,可以减少代码输入量,具体规则自上而下优先应用如下列表:
- null 返回 false
- boolean 类型,原值返回
- String、StringBuilder等一切继承自 CharSequence 类的对象,返回 length > 0
- 其它返回 true
其他
1.逗号表达式:将多个表达式使用逗号分隔开来组合而成的表达式称为逗号表达式,逗号表达式整体求值的结果为最后一个表达式的值。例如:1+2, 3*4 这个逗号表达式的值为12。
2.去除了位运算符,避免开发者在模板引擎中表述过于复杂,保持模板引擎的应用初衷,同时也可以提升性能。
Map定义
Map定义表达式的最实用场景是在调用方法或函数时提供极为灵活的参数传递方式,当方法或函数需要传递的参数名与数量不确定时极为有用.
#set(map = {k1:123, "k2":"abc", "k3":object})
#(map.k1)
#(map.k2)
#(map["k1"])
#(map["k2"])
#(map.get("k1"))
/*注意:上例中使用了标识符 k1 而非 String 常量值 "k1" 只是为了书写时的便利,与字符串是等价的,并不会对标识符 k1 进行表达式求值。*/
key 只允许是合法的 java 变量名标识符或者 String 常量值
还支持在定义的同时进行取值:
#({1:'自买', 2:'跟买'}.get(1))
#({1:'自买', 2:'跟买'}[2])
### 与双问号符联合使用支持默认值
#({1:'自买', 2:'跟买'}.get(999) ?? '其它')
数组定义
数组的定义与初始化,以及数据获取与赋值:
// 定义数组 array,并为元素赋默认值
#set(array = [123, "abc", true])
// 获取下标为 1 的值,输出为: "abc"
#(array[1])
// 将下标为 1 的元素赋值为 false,并输出
#(array[1] = false, array[1])
数组定义表达式的初始化元素除了可以使用常量值以外,还可以使用任意的表达式,包括变量、方法调用返回值:
#set(array = [ 123, "abc", true, a && b || c, 1 + 2, obj.doIt(x) ])
范围数组定义
下面代码表达式 [1…10] 定义了一个范围数组,其值为从1到10的整数数组,该表达式通常用于在开发前端页面时,模拟迭代输出多条静态数据,而又不必从后端读取数据。
#for(x : [1..10])
#(x)
#end