友情提示:本篇总结,主要是便于自己 能有个“全局的认识”。具体学习和使用,参考官网。
经验:对一个事物、事情,如果先有个全局的认识,学习起来快多了。有没有全局认识,也是检验掌握这个事的关键信号。
分享特色:简单、简洁、有用、能用。
一、模版引擎,解决的问题
1、Web开发,前后端不分离时,页面渲染
2、通用场景
邮件模版、短信模版、PDF预览(签字协议、合同)、基于模版的代码生成器
3、常见模版引擎和通用功能
JSP、Freemarker、Velocity、JFinal Enjoy等。
常见功能:表达式、if和for等指令,页面模版,模块化,分页宏,Java方法调用
二、JFinal Enjoy概述
1、官网
https://www.jfinal.com/doc/6-1
这个作者写的各种框架,简单易用。源码,简单、简洁、好理解。
2、官网介绍
Enjoy Template Engine 采用独创的 DKFF (Dynamic Key Feature Forward)词法分析算法以及独创的DLRD (Double Layer Recursive Descent)语法分析算法,极大减少了代码量,降低了学习成本,并提升了用户体验。
与以往任何一款 java 模板引擎都有显著的不同,极简设计、独创算法、极爽开发体验,从根本上重新定义了模板引擎。
3、开发者角度
Java语法,学习成本低。
Enjoy 模板引擎专为 java 开发者打造,所以坚持两个核心设计理念:一是在模板中可以直接与 java 代码通畅地交互,二是尽可能沿用 java 语法规则,将学习成本降到极致。
因此,立即掌握 90% 的用法,只需要记住一句话:JFinal 模板引擎表达式与 Java 是直接打通的。
记住了上面这句话,就可以像下面这样愉快地使用模板引擎了:
// 算术运算
1 + 2 / 3 * 4
// 比较运算
1 > 2
// 逻辑运算
!a && b != c || d == e
// 三元表达式
a > 0 ? a : b
// 方法调用
"abcdef".substring(0, 3)
target.method(p1, p2, pn)
|
Enjoy 模板引擎核心概念只有指令与表达式这两个。而表达式是与 Java 直接打通的,所以没有学习成本,剩下来只有 #if、#for、#define、#set、#include、#switch、#(...) 七个指令
三、表达式
官网例子: https://www.jfinal.com/doc/6-3
1、与java规则基本相同的表达式
算术运算: + - * / % ++ --
比较运算: > >= < <= == != (基本用法相同,后面会介绍增强部分)
逻辑运算: ! && ||
三元表达式: ? :
Null 值常量: null
字符串常量: "jfinal club"
|
2、模版独有的优化,符合直觉的扩展
属性访问:user.name
方法调用:user.getName()
静态属性访问:
#if(x.status == com.demo.common.model.Account::STATUS_LOCK_ID)
<span>(账号已锁定)</span>
#end
|
静态方法调用:
#if(com.jfinal.kit.StrKit::isBlank(title))
....
#end
|
空合并安全取值调用操作符:object.field ?? "默认值"
单引号字符串:
<a href="/" class="#(menu == 'index' ? 'current' : 'normal')"
首页
</a>
|
相等与不等比较表达式增强
#if(name== "fans")
...
#end
|
布尔表达式增强
#if(user && user.id == x.userId)
...
#end
|
Map 定义表达式
#set(map = {k1:123, "k2":"abc", "k3":object})
#(map.k1)
#(map.k2)
#(map["k1"])
#(map["k2"])
#(map.get("k1"))
|
数组定义表达式
// 定义数组 array,并为元素赋默认值
#set(array = [123, "abc", true])
// 获取下标为 1 的值,输出为: "abc"
#(array[1])
// 将下标为 1 的元素赋值为 false,并输出
#(array[1] = false, array[1])
|
范围数组定义表达式
#for(x : [1..10])
#(x)
#end
|
逗号表达式
将多个表达式使用逗号分隔开来组合而成的表达式称为逗号表达式,逗号表达式整体求值的结果为最后一个表达式的值。例如:1+2, 3*4 这个逗号表达式的值为12。
从java中去除的运算符
针对模板引擎的应用场景,去除了位运算符,避免开发者在模板引擎中表述过于复杂,保持模板引擎的应用初衷,同时也可以提升性能。
表达式总结
以上各小节介绍的表达式用法,主要是在 java 表达式规则之上做的有利于开发体验的精心扩展,你也可以先无视这些用法,而是直接当成是 java 表达式去使用,则可以免除掉上面的学习成本。
上述这些在 java 表达式规则基础上做的精心扩展,一是基于模板引擎的实际使用场景而添加,例如单引号字符串。二是对过于啰嗦的 java 语法的改进,例如字符串的比较 str == "james" 取代 str.equals("james"),所以是十分值得和必要的。
四、指令
1、输出指令#( )
#(value)
#(object.field)
#(object.field ??)
#(a > b ? x : y)
#(seoTitle ?? "JFinal 俱乐部")
#(object.method(), null)
|
2、#if 指令
#if(c1)
...
#else if(c2)
...
#else if (c3)
...
#else
...
#end
|
3、#for 指令
// 对 List、数组、Set 这类结构进行迭代
#for(x : list)
#(x.field)
#end
// 对 Map 进行迭代
#for(x : map)
#(x.key)
#(x.value)
#end
|
for指令支持的所有状态值如下示例:
#for(x : listAaa)
#(for.size) 被迭代对象的 size 值
#(for.index) 从 0 开始的下标值
#(for.count) 从 1 开始的记数值
#(for.first) 是否为第一次迭代
#(for.last) 是否为最后一次迭代
#(for.odd) 是否为奇数次迭代
#(for.even) 是否为偶数次迭代
#(for.outer) 引用上层 #for 指令状态
#end
|
for 指令还支持 #else 分支语句
#for(blog : blogList)
#(blog.title)
#else
您还没有写过博客,点击此处<a href="/blog/add">开博</a>
#end
|
4、#switch 指令
#switch (month)
#case (1, 3, 5, 7, 8, 10, 12)
#(month) 月有 31 天
#case (2)
#(month) 月平年有28天,闰年有29天
#default
月份错误: #(month ?? "null")
#end
|
5、#set 指令
set(x = 123)
#set(a = 1, b = 2, c = a + b)
#set(array[0] = 123)
#set(map["key"] = 456)
#(x) #(c) #(array[0]) #(map.key) #(map["key"])
|
6、#include 指令
_hot_list.html
<div class="hot-list">
<h3>#(title)</h3>
<ul>
#for(x : list)
<li>
<a href="#(url)/#(x.id)">#(x.title)</a>
</li>
#end
</ul>
</div>
#include("_hot_list.html", title="热门项目", list=projectList, url="/project")
#include("_hot_list.html", title="热门新闻", list=newsList, url="/news")
|
7、#render 指令
render指令在使用上与include指令几乎一样,同样也支持无限量传入赋值表达式参数,主要有两点不同:
- render指令支持动态化模板参数,例如:#render(temp),这里的temp可以是任意表达式,而#include指令只能使用字符串常量:#include(“abc.html”)
- render指令中#define定义的模板函数只在其子模板中有效,在父模板中无效,这样设计非常有利于模块化
引入 #render 指令的核心目的在于支持动态模板参数。
8、#define 指令
layout.html
#define layout()
<html>
<head>
<title>JFinal俱乐部</title>
</head>
<body>
#@content()
</body>
</html>
#end
#include("layout.html")
#@layout()
#define content()
<div>
这里是模板内容部分,相当于传统模板引擎的 nested 的部分
</div>
#end
|
上图中的第一行代码表示将前面创建的模板文件layout.html包含进来,第二行代码表示调用layout.html中定义的layout模板函数,而这个模板函数中又调用了content这个模板函数,该content函数已被定义在当前文件中,简单将这个过程理解为函数定义与函数调用就可以了。
10、#date 指令
#date(account.createAt)
#date(account.createAt, "yyyy-MM-dd HH:mm:ss")
|
后端把日期格式化,前端直接展示,可能更好。
11、#number 指令
#number(3.1415926, "#.##")
#number(0.9518, "#.##%")
#number(300000, "光速为每秒,### 公里。")
|
后端把数字格式化,前端直接展示,可能更好。
12、#escape 指令
escape 指令用于 html 安全转义输出,可以消除 XSS 攻击。escape 将类似于 html 形式的数据中的大于号、小于号这样的字符进行转义,例如将小于号转义成:< 将空格转义成
使用方式与输出指令类似:
#escape(blog.content)
13、指令扩展
查看官网
五、扩展
1、共享方法Shared Method 扩展
public void configEngine(Engine me) {
me.addSharedMethod(new com.jfinal.kit.StrKit());
}
#if(isBlank(nickName))
...
#end
#if(notBlank(title))
...
#end
|
默认 Shared Method 配置扩展
Enjoy 模板引擎默认配置添加了 com.jfinal.template.ext.sharedmethod.SharedMethodLib 为 Shared Method,所以其中的方法可以直接使用不需要配置。里头有 isEmpty(...) 与 notEmpty(...) 两个方法可以使用。
isEmpty(...) 用来判断 Collection、Map、数组、Iterator、Iterable 类型对象中的元素个数是否为 0。
#if ( isEmpty(list) )
list 中的元素个数等于 0
#end
#if ( notEmpty(map) )
map 中的元素个数大于 0
#end
|
2、共享对象Shared Object扩展
public void configEngine(Engine me) {
me.addSharedObject("RESOURCE_HOST", "http://res.jfinal.com");
me.addSharedObject("sk", new com.jfinal.kit.StrKit());
}
<img src="#(RESOURCE_HOST)/img/girl.jpg" />
#if(sk.isBlank(title))
...
#end
|
3、方法扩展Extension Method
public class MyIntegerExt {
public Integer square(Integer self) {
return self * self;
}
public Double power(Integer self, Double exponent) {
return Math.pow(self, exponent);
}
public Boolean isOdd(Integer self) {
return self % 2 != 0;
}
}
Engine.addExtensionMethod(Integer.class, MyIntegerExt.class);
#set(num = 123)
#(num.square())
#(num.power(10))
#(num.isOdd())
|
六、辅助功能
1、注释
### 这里是单行注释
#--
这里是多行注释的第一行
这里是多行注释的第二行
--#
|
2、原样输出
#[[
#(value)
#for(x : list)
#(x.name)
#end
]]#
|
无论是单行注释、多行注释,还是原样输出,都是以三个字符开头,目的都是为了降低与纯文本内容冲突的概率。
七、环境和使用
1、任意环境
Enjoy Template Engine 的使用不限于 web,可以使用在任何 java 开发环境中。Enjoy 常被用于代码生成、email 生成、模板消息生成等具有模板特征数据的应用场景,使用方式极为简单。
Engine engine = Engine.use();
engine.setDevMode(true);
engine.setToClassPathSourceFactory();
engine.getTemplate("index.html");
|
更多用法,参考官网:
https://www.jfinal.com/doc/6-11
2、Spring整合和SpringBoot整合
https://www.jfinal.com/doc/6-10
Maven坐标、SpringMVC整合、SpringBoot整合
3、JFinal整合
使用JFinal的,自然知道官网。
八、各种模版引擎语法对比
本来打算认真对比下的,但是:JSP、Velocity、Freemarker语法,完全记不住。之前的代码已经不像再翻了,全都是过去时了。
掌握一种最满足需要的JFinal Enjoy,就足够了。
常见模版引擎痛点:语法复杂记不住、语法和Java语法相近但又不同、“美元符号$” ${page.totalPage} 经常和jQuery之类的框架冲突。
九、JFinal enjoy实际例子,最常用的语法
for循环展示 一页 内容
if else语句,文章有封面图,就需要缩略图展示
表达式,#(post.cover)
内容分页栏,定义单独的函数fnPage
include包含通用页面
#for(post : postPage.rows)
<div class="item">
<a href="/post/detail/#(post.id).html">
<!-- resize,w_150 x-oss-process=image/resize,m_fixed,h_100,w_100 -->
#if(post.cover!=null)
<img src="#(post.cover)?x-oss-process=image/resize,m_fixed,w_160,h_100">
#else
<img src="../res/static/images/news_img13.jpg">
#end
</a>
<div class="item-info">
<h4><a href="/post/#(post.id).html">#(post.title)</a></h4>
<div class="b-txt">
#include("common/categoryLink.html")
<span class="icon message">
<i class="layui-icon layui-icon-dialogue"></i>
500条
</span>
<span class="icon time">
<i class="layui-icon layui-icon-log"></i>
10分钟前
</span>
</div>
</div>
</div>
#end
#@fnPage("/list",postPage)
#include("common/recommendBar.html")
|
<!-- jfinal enjoy模版,分页函数,只负责展示,不存在计算 -->
<!-- layui 分页样式 -->
#define fnPage(url,page)
<div id="micronews-details-test" style="text-align: center;">
<div class="layui-box layui-laypage layui-laypage-default" id="layui-laypage-1">
#if(page.current == 1)>
<a href="javascript;" class="layui-laypage-prev layui-disabled">首页</a>
<a href="javascript;" class="layui-laypage-prev layui-disabled">上一页</a>
#else
<a href="#@buildPageUrl(url,1,page)">首页</a>
<a href="#@buildPageUrl(url,page.current-1,page.size)">上一页</a>
#end
#for(pageNo = page.startNumber; pageNo <= page.endNumber; pageNo++)
#if(page.current== pageNo)
<span class="layui-laypage-curr"><em class="layui-laypage-em"></em><em>#(pageNo)</em></span>
#else
<a href="#@buildPageUrl(url,pageNo,page.size)">#(pageNo)</a>
#end
#end
#if (page.current == page.pages)
<a href="javascript;" class="disabled" class="layui-laypage-prev layui-disabled">下一页</a>
<a href="javascript;" class="disabled" class="layui-laypage-prev layui-disabled">尾页</a>
#else
<a href="#@buildPageUrl(url,page.current+1,page.size)">下一页</a>
<a href="#@buildPageUrl(url,page.pages,page.size)">尾页</a>
#end
<span>共#(page.pages)页</span>
</div>
</div>
#end
#define buildPageUrl(url,current,size)
#if(url.contains('?'))
#(url)¤t=#(current)&size=#(size)
#else
#(url)?current=#(current)&size=#(size)
#end
#end
|