Foxnic-SQL (13) —— 内部SQL与SQL模版

概述

  在前几节中,曾经介绍过 Foxnic-SQL 将 SQL 语句对象化并执行。那么,为什么还要引入内部 SQL 和 SQL 模板的个性呢?
  首先,大多数时候,咱们的第一反馈是用字符串去拼接 SQL 语句,这阐明字符串拼接形式其实是最直观的。其次,应用对象化的形式拼接 SQL ,还是有其局限性,大量的 SQL 文本也不宜间接写在 Java 类中。所以,Foxnic-SQL 将本来要写在 Java 类中的 SQL 语句放到一个内部文件中,每个语句用一个 ID 去标识,在 SQL 执行时,只有指定 ID 就能够了。在此基础上,Foxnic-SQL 退出了模板引擎、SQL 语句置换、热加载等个性,使其变得更加好用。
  SQL 模板的设计目标是在 SQL 语句外置的同时,能够在语句中退出逻辑、替换变量、置换语句。SQL 模板对象同时反对内置和外置的SQL语句,当然大多数状况下,模板SQL都是内部语句。

  本文中的示例代码均可在 https://gitee.com/LeeFJ/foxnic-samples 我的项目中找到。

应用内部SQL

  在调用内部语句前,首先要定义好内部语句,内部语句能够定义在 Java 文件同一个包里,如图所示:
  
  上面的示例,我要在 TemplateTop 类内执行 db0.tql 内定义的 SQL 语句。以下是 db0.tql 文件的局部内容:

[query_0]select 'moduleTop@query_0'[top_query_1]select 'moduleTop@query_1'

  如上代码所示,语句的 ID 被写在 SQL 语句上方的中括号内,这个 ID 将在 Java 代码内被应用。以下是 TemplateTop 类的局部代码:

public class TemplateTop {    public void initSQLoader(DAO dao) {        // 设置SQL扫描范畴        SQLoader.addTQLScanPackage(dao, TemplateTop.class.getPackage().getName());        SQLoader.setPrintDetail(true);    }    /*** 简略示例* */    @Test    public void demo_0() {        // 创立 DAO        DAO dao = DBInstance.DEFAULT.dao();        // 初始化 SQLoader        initSQLoader(dao);        String result = null;        // 执行以后包下的语句        result = dao.queryString("#query_0");        System.out.println(result);    }}

  TemplateTop 类中蕴含了两个办法,其中 initSQLoader 办法用于初始化SQL扫描器。应用 SQL 扫描器时须要增加扫描的 package ,被增加包以及子包均会被扫描。
  demo_0 办法用于执行语句,以#结尾的 SQL ID 既示意要执行的不是 SQL 语句,而是指定一个 SQL ID 并加载到曾经定义好的 SQL 语句去执行。

内部SQL的作用域

  内部定义的 SQL 也是有其作用域的,艰深点讲就是命名空间,每个定义的 SQL ID 并非在利用内全局无效,而只是在其包内或父级包内才无效,有点相似继承的成果。
  
  如上图所示,在 template 包内有两个子包,TemplateTop 仅能够拜访 db_top.tql 内定义的 SQL 语句。而 TemplateM1 仅能够拜访 db_m1_tql 和 db_top.tql 内定义的 SQL,而不能够拜访 db_m2_tql 内定义的 SQL 语句。上面咱们来看一下在 TemplateM1 内可见和不可见的 SQL 语句示例:

public class TemplateM1 extends TemplateTop {    @Test    public void  scope_demo_in_M1() {        // 创立 DAO        DAO dao= DBInstance.DEFAULT.dao();        // 初始化 SQLoader        initSQLoader(dao);        //        String result = null;        result = dao.queryString("#query_0");        System.out.println(result);        assertTrue("module1@query_0".equals(result));        result = dao.queryString("#m1_query_1");        System.out.println(result);        assertTrue("module1@query_1".equals(result));        // 容许执行下级级包内定义的语句        result = dao.queryString("#top_query_1");        System.out.println(result);        assertTrue("moduleTop@query_1".equals(result));        // 不容许执行其它同级包内定义的语句        try {            dao.queryString("#m2_query_1");            assertTrue(false);        } catch (Exception e) {            assertTrue(true);        }    }}

  同样,咱们能够看一下 TemplateTop 可见和不可见的 SQL 语句示例:

public class TemplateTop {    public void initSQLoader(DAO dao) {        // 设置SQL扫描范畴        SQLoader.addTQLScanPackage(dao, TemplateTop.class.getPackage().getName());        SQLoader.setPrintDetail(true);    }    @Test    public void  scope_demo_in_Top() {        // 创立 DAO        DAO dao= DBInstance.DEFAULT.dao();        // 初始化 SQLoader        initSQLoader(dao);        String result = null;        // 执行以后包下的语句        result = dao.queryString("#query_0");        System.out.println(result);        assertTrue("moduleTop@query_0".equals(result));        // 不容许执行上级包内定义的语句        try {            dao.queryString("#m1_query_1");            assertTrue(false);        } catch (Exception e) {            assertTrue(true);        }    }}

  因为篇幅所限,对于内部语句命名空间的两个示例未给出残缺示例,残缺代码请大家到 https://gitee.com/LeeFJ/foxnic-samples 我的项目中查看。

应用SQL模板

  SQL 模板(SQLTpl)用于更为简单的内部 SQL 拼装。SQLTpl 首先应用模板引擎对既定的语句进行解决,再对 SQL 语句进行置换,最初代入绑定变量执行语句。以下代码首先定义了一个内部 SQL 模板:

[top_query_3]select #for(f:fields)#(f)#(for.last?' ':', ')#endfrom sys_dict_item itmLEFT JOIN sys_dict dict ON dict.code=itm.dict_codeWHERE dict.code like ?#{QUESTION_LIST_1}and itm.code like ?#{QUESTION_LIST_2}and valid = ?order by dict.id asc

  SQLTpl 应用 了 JFinal Enjoy 的模板引擎,对于其模板引擎相干指令请大家移步 https://jfinal.com/doc/6-1 自行查看。Foxnic-SQL 额定减少了 #{} 指令,用于 SQL 表达式的置换。示例如下:

/*** 获取并解决模板对象* */@Testpublic void  demo_2_template() {    // 创立 DAO    DAO dao= DBInstance.DEFAULT.dao();    // 初始化 SQLoader    initSQLoader(dao);    // 取得模板对象    SQLTpl template= dao.getTemplate("#top_query_3");    // 设置模板变量    template.putVar("fields",new String[]{"itm.code","label"});    // 设置 SQL 置换对象    template.putVar("QUESTION_LIST_1",new Expr("and itm.label like ?","%修%"));    template.putVar("QUESTION_LIST_2",new Expr("and itm.code like ?","%r%"));    // 设置模板中的 SQL 绑定变量    template.setParameters("%e%","%r%",1);    // 输入    System.out.println(template.getSQL());    // 执行查问,利用 QueryableSQL 接口个性    RcdSet rs=template.query();    // 遍历与输入    for (Rcd r : rs) {        System.out.println(r.toJSONObject());    }}

数据库适配

  Foxnic-SQL 利用内部 SQL 解决数据库适配的问题,Foxnic-SQL 在 DAO 以及 Service 中主动生成的 SQL 语句是多库兼容的。然而,某些手写的语句要做到多库兼容就须要一些额定的解决,在内部 SQL 定义时,能够指定执行的数据库类型:

[top_query_2:mysql]select 'moduleTop@query_2#mysql'[top_query_2]select 'moduleTop@query_2#default'

  如上代码所示,第一个语句的 SQL ID 冒号前面 mysql 指定的数据库类型。这样,默认状况下执行第五行的 SQL 语句,但若以后库是 mysql 那么就执行第二行的 SQL 语句。如下代码所示:

/*** 执行特定数据库的语句* */@Testpublic void  demo_1_dbType() {    // 创立 DAO    DAO dao= DBInstance.DEFAULT.dao();    // 初始化 SQLoader    initSQLoader(dao);    String result = null;    // 执行以后包下的语句    result = dao.queryString("#top_query_2");    System.out.println(result);    if(dao.getDBType()== DBType.MYSQL) {        assertTrue("moduleTop@query_2#mysql".equals(result));    } else {        assertTrue("moduleTop@query_2#default".equals(result));    }}

小结

  本节次要介绍了在 Foxnic-SQL 中外置的 SQL 语句与 SQL 模板,同时也简略介绍了 Foxni-SQL 对于多库适配的反对形式。Foxnic-SQL 中外置 SQL 是对语句对象化补充与扩大,在一些简单 SQL 且不适宜对象化 SQL 的场合较为实用。

相干我的项目

  https://gitee.com/LeeFJ/foxnic
  https://gitee.com/LeeFJ/foxnic-web
  https://gitee.com/lank/eam
  https://gitee.com/LeeFJ/foxnic-samples

官网文档

  http://foxnicweb.com/docs/doc.html

附录(Enjoy 模版引擎应用摘要)

  Enjoy 模版引擎相干指令,如要学习 Enjoy 模版引擎,请移步 https://jfinal.com/doc/6-1

6.4 指令

  Enjoy Template Engine判若两人地保持极简设计,外围只有 #if、#for、#switch、#set、#include、#define、#(…) 这七个指令,便实现了传统模板引擎简直所有的性能,用户如果有任意一门程序语言根底,学习老本简直为零。
  如果官网提供的指令无奈满足需要,还能够极其简略地在模板语言的层面对指令进行扩大,在com.jfinal.template.ext.directive 包上面就有五个扩大指令,Active Record 的 sql 模块也针对sql治理性能扩大了三个指令,参考这些扩大指令的代码,便可无师自通,极为简略。
  留神,Enjoy 模板引擎指令的扩大是在词法剖析、语法分析的层面进行扩大,与传统模板引擎的自定义标签类的扩大齐全不是一个级别,前者能够极为全面和自在的利用模板引擎的基础设施,在更加根底的层面以极为简略间接的代码实现变幻无穷的性能。参考 Active Record的 sql 治理模块,则可知其弱小与便当。

1、输入指令#( )

  与简直所有 java 模板引擎不同,Enjoy Template Engine毁灭了插值指令这个本来独立的概念,而是将其当成是所有指令中的一员,仅仅是指令名称省略了而已。因而,该指令的定界符与一般指令一样为小括号,从而不用像其它模板引擎一样引入额定的如大括号般的定界符。
  #(…) 输入指令的应用极为简略,只须要为该指令传入后面6.4节中介绍的任何表达式即可,指令会将这些表达式的求值后果进行输入,特地留神,当表达式的值为 null 时没有任何输入,更不会报异样。所以,对于 #(value) 这类输入不须要对 value 进行 null 值判断,如下是代码示例:

#(value)#(object.field)#(object.field ??)#(a > b ? x : y)#(seoTitle ?? "JFinal 俱乐部")#(object.method(), null)

  如上图所示,只须要对输入指令传入表达式即可。留神上例中第一行代码 value 参数能够为 null,而第二行代码中的 object 为 null 时将会报异样,此时须要应用第三行代码中的空合平安取值调用运算符:object.field ??
  此外,留神上图最初一行代码中的输入指令参数为一个逗号表达式,逗号表达式的整体求值后果为最初一个表达式的值,而输入指令对于null值不做输入,所以这行代码相当于是仅仅调用了 object.method() 办法去实现某些操作。
  输入指令能够自在定制,只须要继承 OutputDirectiveFactory 类并笼罩其中的 getOutputDirective 办法,而后在 configEngine(Engine me)办法中,通过 me. setOutputDirectiveFactory(…) 切换即可。

2、#if 指令

  间接举例:
  如上图所示,if指令须要一个 cond 表达式作为参数,并且以 #end 为结尾符,cond 能够为 6.3 章节中介绍的所有表达式,包含逗号表达式,当 cond 求值为 true 时,执行 if 分支之中的代码。
  if 指令必然反对 #else if 与 #else 分支块构造,以下是示例:
  因为#else if、#else用法与java语法齐全一样,在此不在赘述。(留神:jfinal 3.3 之前的版本 #else if 之间不能有空格字符须要写成:#elseif,否则会报异样:Can not match the #end of directive #if )

3、#for 指令

  Enjoy Template Engine 对 for 指令进行了极为人性化的扩大,能够对任意类型数据进行迭代输入,包含反对 null 值迭代。以下是代码示例:
  上例代码中的第一个 for 指令是对 list 进行迭代,用法与 java 语法齐全一样。
  第二个 for 指令是对 map 进行迭代,取值形式为 item.key 与 item.value。该取值形式是 enjoy 对 map 迭代的加强性能,能够节俭代码量。依然也能够应用传统的 java map 迭代形式:#for( x : map.entrySet() ) #(x.key) #(x.value) #end
  留神:当被迭代的指标为 null 时,不须要做 null 值判断,for 指令会主动跳过,不进行迭代。从而能够防止 if 判断,节俭代码提高效率。
  for指令还反对对其状态进行获取,代码示例:
  以上代码中的 #(for.index)、#(for.outer.index) 是对 for 指令以后状态值进行获取,前者是获取以后 for 指令迭代的下标值(从0开始的整数),后者是内层for指令获取上一层for指令的状态。这里留神 for.outer 这个固定的用法,专门用于在内层 for 指令中援用下层for指令状态。
   留神:for指令嵌套时,各自领有本人的变量名作用域,规定与java语言统一,例如上例中的两个#(x.field)处在不同的for指令作用域内,会正确获取到所属作用域的变量值。
  for指令反对的所有状态值如下示例:
  具体用法在下面代码中用中文进行了阐明,在此不再赘述。
  除了 Map、List 以外,for指令还反对 Collection、Iterator、array 一般数组、Iterable、Enumeration、null 值的迭代,用法在模式上与后面的List迭代完全相同,都是 #for(id : target) 的模式,对于 null 值,for指令会间接跳过不迭代。
  此外,for指令还反对对任意类型进行迭代,此时仅仅是对该对象进行一次性迭代,如下所示:
  上例中的article为一个一般的java对象,而非汇合类型对象,for循环会对该对象进行一次性迭代操作,for表达式中的x即为article对象自身,所以能够应用 #(x.title) 进行输入。
  for 指令还反对 #else 分支语句,在for指令迭代次数为0时,将执行 #else 分支外部的语句,如下是示例:
  以上代码中,当blogList.size() 为0或者blogList为null值时,也即迭代次数为0时,会执行#else分支,这种场景在web我的项目中极为常见。
  最初,除了下面介绍的for指令迭代用法以外,还反对更惯例的for语句模式,以下是代码示例:
  与java语法根本一样,惟一的不同是变量申明不须要类型,间接用赋值语句即可,Enjoy Template Engine中的变量是动静弱类型。
  留神:以上这种模式的for语句,比后面的for迭代少了for.size与for.last两个状态,只反对如下几个状态:for.index、for.count、for.first、for.odd、for.even、for.outer
  #for 指令还反对 #continue、#break 指令,用法与java完全一致,在此不再赘述。

4、#switch 指令

  #switch 指令对标 java 语言的 switch 语句。根本用法统一,但做了少许晋升用户体验的改良,用法如下:
  如上代码所示,#case 分支指令反对以逗号分隔的多个参数,这个性能就消解掉了 #break 指令的必要性,所以 enjoy 模板引擎是不须要 #break 指令的。
  #case 指令参数还能够是任意表达式,例如:
  上述代码中用逗号分隔的表达式先会被求值,而后再逐个与 #switch(value) 指令中的 value 进行比拟,只有有一个值与其相等则该 case 分支会被执行。
  #case 反对逗号分隔的多参数,从而无需引入 #break 指令,不仅缩小了代码量,而且防止了忘写 #break 指令时带来的谬误隐患。还有一个与 java 语法有区别的中央是 #case、#default 指令都未应用冒号字符。

5、#set 指令

set指令用于申明变量同时对其赋值,也能够是为已存在的变量进行赋值操作。set指令只承受赋值表达式,以及用逗号分隔的赋值表达式列表,如下是代码示例:
  以上代码中,第一行代码最为简略为x赋值为123,第二行代码是一个赋值表达式列表,会从左到右顺次执行赋值操作,如果等号左边呈现表达式,将会对表达式求值当前再赋值。最初一行代码是输入上述赋值当前各变量的值,其她所有指令也能够像输入指令一样进行变量的拜访。
  请留神,#for、#include、#define这三个指令会开启新的变量名作用域,#set指令会首先在本作用域中查找变量是否存在,如果存在则对本作用域中的变量进行操作,否则持续向下层作用域查找,找到则操作,如果找不到,则将变量定义在顶层作用域中,这样设计十分有利于在模板中传递变量的值。
  当须要明确指定在本层作用域赋值时,能够应用#setLocal指令,该指令所需参数与用法与#set指令齐全一样,只不过作用域被指定为以后作用域。#setLocal 指令通常用于#define、#include指令之内,用于实现模块化,从而心愿其中的变量名不会与下层作用域产生命名上的抵触。
  重要:因为赋值表达式实质也是表达式,而其它指令实质上反对任意表达式,所以 #set 指令对于赋值来说并不是必须的,例如能够在 #() 输入指令中应用赋值表达式:
  以上代码在输入指令中应用了多个赋值表达式,能够实现 #set 的性能,在最初通过一个 null 值来防止输入表达式输入任何货色。相似的,别的指令外部也能够这么来应用赋值表达式。

6、#include 指令

  include指令用于将内部模板内容蕴含进来,被蕴含的内容会被解析成为以后模板中的一部分进行应用,如下是代码示例:
  #include 指令第一个参数必须为 String 常量,当以 ”/” 打头时将以 baseTemplatePath 为相对路径去找文件,否则将以应用 #include 指令的以后模板的门路为相对路径去找文件。
  baseTemplatePath 能够在 configEngine(Engine me) 中通过 me.setBaseTemplatePath(…) 进行配置。
  此外,include指令反对传入有限数量的赋值表达式,非常有利于模块化,例如:如下名为 ”_hot_list.html” 的模板文件用于展现热门我的项目、热门新闻等等列表:
  上图中的 title、list、url 是该html片段须要的变量,应用include指令别离渲染“热门我的项目”与“热门新闻”的用法如下:
  下面两行代码中,为“_hot_list.html”中用到的三个变量title、list、url别离传入了不同的值,实现了对“_hot_list.html”的模块化重用。

7、#render 指令

  render指令在应用上与include指令简直一样,同样也反对无限量传入赋值表达式参数,次要有两点不同:

  • render指令反对动态化模板参数,例如:#render(temp),这里的temp能够是任意表达式,而#include指令只能应用字符串常量:#include(“abc.html”)
  • render指令中#define定义的模板函数只在其子模板中无效,在父模板中有效,这样设计十分有利于模块化

  引入 #render 指令的外围目标在于反对动静模板参数。

8、#define 指令

  #define指令是模板引擎次要的扩大形式之一,define指令能够定义模板函数(Template Function)。通过define指令,能够将须要被重用的模板片段定义成一个一个的 template function,在调用的时候能够通过传入参数实现变幻无穷的性能。
  在此给出应用define指令实现的layout性能,首先创立一个layout.html文件,其中的代码如下:

#define layout()<html>  <head>    <title>JFinal俱乐部</title>  </head>  <body>    #@content()  </body></html>#end

  以上代码中通过#define layout()定义了一个名称为layout的模板函数,定义以#end结尾,其中的 #@content() 示意调用另一个名为 content 的模板函数。
  特地留神:模板函数的调用比指令调用多一个@字符,是为了与指令调用辨别开来。
  接下来再创立一个模板文件,如下所示:
  上图中的第一行代码示意将后面创立的模板文件layout.html蕴含进来,第二行代码示意调用layout.html中定义的layout模板函数,而这个模板函数中又调用了content这个模板函数,该content函数已被定义在以后文件中,简略将这个过程了解为函数定义与函数调用就能够了。留神,上例实现layout性能的模板函数、模板文件名称能够任意取,不用像velocity、freemarker须要记住 nested、layoutContent这样无聊的概念。
  通常作为layout的模板文件会在很多模板中被应用,那么每次应用时都须要#include指令进行蕴含,实质上是一种代码冗余,能够在configEngine(Engine me)办法中,通过me.addSharedFunction("layout.html")办法,将该模板中定义的所有模板函数设置为共享的,那么就能够省掉#include(…),通过此办法能够将所有罕用的模板函数全副定义成相似于共享库这样的汇合,极大进步重用度、缩小代码量、晋升开发效率。
  Enjoy Template Engine彻底毁灭掉了layout、nested、macro这些无聊的概念,极大升高了学习老本,并且极大晋升了扩大能力。模板引擎实质是一门程序语言,任何可用于生产环境的语言能够像呼吸空气一样自在地去实现 layout 这类性能。
  此外,模板函数必然反对形参,用法与java规定基本相同,惟一不同的是不须要指定参数类型,只须要参数名称即可,如下是代码示例:
  以上代码中的模板函数test,有a、b、c三个形参,在函数体内仅简略对这三个变量进行了输入,留神形参必须是非法的java标识符,形参的作用域为该模板函数之内合乎绝大多数程序语言习惯,以下是调用该模板函数的例子代码:
  以上代码中,第一个参数传入的整型123,第二个是字符串,第三个是一个 field 取值表达式,从例子能够看出,实参能够是任意表达式,在调用时模板引擎会对表达式求值,并逐个赋值给模板函数的形参。
  留神:形参加实参数量要雷同,如果实参偶然有更多不确定的参数要传递进去,能够在调用模板函数代码之前应用#set指令将值传递进去,在模板函数外部可用空合平安取值调用表达式进行适当管制,具体用法参考 jfinal-club 我的项目中的 _paginate.html 中的 append 变量的用法。
  define 还反对 return 指令,能够在模板函数中返回,但不反对返回值。

9、模板函数调用与 #call 指令

  调用define定义的模板函数的格局为:#@name(p1, p2…, pn),模板函数调用比指令调用多一个@字符,多出的@字符用来与指令调用区别开来。
  此外,模板函数还反对平安调用,格局为:#@name?(p1, p2…, pn),平安调用只需在模板函数名前面增加一个问号即可。平安调用是指当模板函数未定义时不做任何操作。
  平安调用适宜用于一些模板中可有可无的内容局部,以下是一个典型利用示例:
  以上代码示例定义了一个web利用的layout模板,留神看其中的两处:#@css?() 与 #@js?() 就是模板函数平安调用。
  上述模板中引入的 jfinal.css 与 jfinal.js 是两个必须的资源文件,对大部分模块曾经满足需要,但对于有些模块,除了须要这两个必须的资源文件以外,还须要额定的资源文件,那么就能够通过#define css() 与 #define js() 来提供,如下是代码示例:
  以上代码中先是通过#@layout()调用了后面定义过的layout()这个模板函数,而这个模板函数中又别离调用了#@main()、#@css?()、#@js?()这三个模板函数,其中后两个是平安调用,所以对于不须要额定的css、js文件的模板,则不须要定义这两个办法,平安调用在调用不存在的模板函数时会间接跳过。
  #call 指令是 jfinal 3.6 版本新增指令,应用 #call 指令,模板函数的名称与参数都能够动静指定,晋升模板函数调用的灵活性,用法如下:
  上述代码中的 funcName 为函数名,p1、p2、pn 为被调用函数所应用的参数。如果心愿模板函数不存在时疏忽其调用,增加常量值 true 在第一个参数地位即可:

10、#date 指令

  date指令用于格式化输入日期型数据,包含Date、Timestamp等所有继承自Date类的对象的输入,应用形式极其简略:
  下面的第一行代码只有一个参数,那么会依照默认日期格局进行输入,默认日期格局为:“yyyy-MM-dd HH:mm”。下面第二行代码则会按第二个参数指定的格局进行输入。
  如果心愿扭转默认输入格局,只须要通过engine.setDatePattern()进行配置即可。
   keepPara 问题:如果日期型表单域提交到后端,而后端调用了 Controller 的 keepPara() 办法,会将这个日期型数据转成 String 类型,那么 #date(...) 指令在输入这个 keepPara 过去的 String 时就会抛出异样,对于这种状况能够指令 keep 住其类型:
  如上所示,第二行代码用 Date.class 参数额定指定了 createAt 域 keep 成 Date 类型,那么在页面 #date(createAt) 指令就不会抛出异样了。keepModel(...)、keepBean(...) 会放弃原有类型,无需做上述解决。

11、#number 指令

  number 指令用于格式化输入数字型数据,包含 Double、Float、Integer、Long、BigDecimal 等所有继承自Number类的对象的输入,应用形式仍然极其简略:
  下面的 #number指令第一个参数为数字类型,第二个参数为String类型的pattern。Pattern参数的用法与JDK中DecimalFormat中pattern的用法齐全一样。当不晓得如何应用pattern时能够在搜索引擎中搜寻关键字DecimalFormat,能够找到十分多的材料。
  #number指令的两个参数能够是变量或者简单表达式,上例参数中应用常量仅为了不便演示。

12、#escape 指令

  escape 指令用于 html 平安本义输入,能够打消 XSS 攻打。escape 将相似于 html 模式的数据中的大于号、小于号这样的字符进行本义,例如将小于号本义成:< 将空格本义成  
  应用形式与输入指令相似:

13、指令扩大

  因为采纳独创的 DKFF 和 DLRD 算法,Enjoy Template Engine 能够极其便当地在语言层面对指令进行扩大,而代码量少到不可设想的境地,学习老本有限迫近于 0。以下是一个代码示例:
  以上代码中,通过继承Directive并实现exec办法,三行代码即实现一个#now指令,能够向模板中输入以后日期,在应用前只需通过me.addDirective(“now”, NowDirective.class) 增加到模板引擎中即可。以下是在模板中应用该指令的例子:
  除了反对上述无#end块,也即无指令body的指令外,Enjoy Template Engine还间接反对蕴含#end与body的指令,以下是示例:
  如上所示,Demo继承Directive笼罩掉父类中的hasEnd办法,并返回true,示意该扩大指令具备#end结尾符。上例中public void exec 办法中的三行代码,其中stat.exec(…)示意执行指令body中的代码,而该办法前后的write(…)办法别离输入一个字符串,最终的输入后果详见前面的应用示例。此外通过笼罩父类的setExprList(…)办法能够对指令的参数进行管制,该办法并不是必须的。
  通过me.addDirective(“demo”, Demo.class)增加到引擎当前,就能够像如下代码示例中应用:
  最初的输入后果如下:
  上例中的#demo指令body中蕴含一串字符,将被Demo.exec(…)办法中的stat.exec(…)所执行,而stat.exec(…)前后的write(…)两个办法调用产生的后果与body产生的后果生成了最终的后果。
  重要:指令中申明的属性是全局共享的,所以要保障指令中的属性是线程平安的。如下代码以 com.jfinal.template.ext.directive.DateDirective 的代码片段为例:
  以上代码中有三个属性,类型是 Expr、Expr、int,其中 Expr 是线程平安的,而 int paraNum 尽管外表上看不是线程平安的,但在整个 DateDirective 类中只有构造方法对该值初始化的时候有写入操作,其它所有中央都是读操作,所以该 int 属性在这里是线程平安的。

6.5 正文

JFinal Template Engine反对单行与多行正文,以下是代码示例:

### 这里是单行正文 #--   这里是多行正文的第一行   这里是多行正文的第二行--#

  如上所示,单行正文应用三个#字符,多行正文以#--打头,以--#结尾。
  与传统模板引擎不同,这里的单行正文采纳三个字符,次要是为了缩小与文本内容相冲突的可能性,模板是极其自由化的内容,应用三个字符,抵触的概率升高一个数量级。
   jfinal 4.4 之前的版本留神:正文在与指令放在同一行时,输入后果会删掉正文前方的换行字符,例如:
  以上模板的输入后果是:"AAABBB",如果心愿输入后果严格遵守模板中的换行,只需将正文独自放在一行,例如:

  以上模板的输入后果将会带有严格的换行,后果如下:
  多行正文与单行正文也相似,只需将其独自放即可。
  除了以上状况以外,其它任何状况都是严格按模板换行输入的,不用关注。jfinal 4.4 版本解决了此问题,倡议降级到 4.4 或更高版本

6.6 原样输入

  原样输入是指不被解析,而仅仅当成纯文本的内容区块,如下所示:

#[[   #(value)   #for(x : list)      #(x.name)   #end]]#

  如上所示,原样输入以 #[[ 三个字符打头,以 ]]# 三个字符结尾,两头被包裹的内容尽管是指令,但依然被当成是纯文本,这十分有利于解决与前端javascript模板引擎的指令抵触问题。
  无论是单行正文、多行正文,还是原样输入,都是以三个字符结尾,目标都是为了升高与纯文本内容抵触的概率。
   留神:用于正文、原样输入的三个控制字符之间不能有空格