友情提示:本篇总结,主要是便于自己 能有个“全局的认识”。具体学习和使用,参考官网。
经验:对一个事物、事情,如果先有个全局的认识,学习起来快多了。有没有全局认识,也是检验掌握这个事的关键信号。
分享特色:简单、简洁、有用、能用。
一、模版引擎,解决的问题
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 指令的核心目的在于支持动态模板参数。
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:void(0);" class = "layui-laypage-prev layui-disabled" >首页</a> <a href= "javascript:void(0);" 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:void(0);" class = "disabled" class = "layui-laypage-prev layui-disabled" >下一页</a> <a href= "javascript:void(0);" 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 |