友情提示:本篇总结,主要是便于自己 能有个“全局的认识”。具体学习和使用,参考官网。

经验:对一个事物、事情,如果先有个全局的认识,学习起来快多了。有没有全局认识,也是检验掌握这个事的关键信号。

 

分享特色:简单、简洁、有用、能用。

一、模版引擎,解决的问题

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(cond)
...
#end
#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 指令

#include("sidebar.html")

_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 形式的数据中的大于号、小于号这样的字符进行转义,例如将小于号转义成:&lt;  将空格转义成 &nbsp;

    使用方式与输出指令类似:

#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