当前位置: 首页 > 工具软件 > Enjoy > 使用案例 >

JFinal Enjoy:专为Java开发者打造的模版引擎,Java语法,快速掌握

能文华
2023-12-01

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

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

 

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

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

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(03)

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 (135781012)

    #(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: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)&current=#(current)&size=#(size)

      #else

           #(url)?current=#(current)&size=#(size)

      #end

#end

 类似资料: