### 基本用法 能通过本章知道Beetl的基本用法 #### 安装 如果使用maven,请使用如下坐标 ```xml <dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl</artifactId> <version>${version}</version> </dependency> ``` 最新版本可以从https://search.maven.org/ 中查询到 #### 快速开始 ```java //初始化代码 StringTemplateResourceLoader resourceLoader = new StringTemplateResourceLoader(); Configuration cfg = Configuration.defaultConfiguration(); GroupTemplate gt = new GroupTemplate(resourceLoader, cfg); //获取模板 Template t = gt.getTemplate("hello,${name}"); t.binding("name", "beetl"); //渲染结果 String str = t.render(); System.out.println(str); ``` Beetl的核心是GroupTemplate,是一个重量级对象,实际使用的时候建议使用单模式创建,创建GroupTemplate需要俩个参数,一个是模板资源加载器,一个是配置类,模板资源加载器Beetl内置了6种,分别是 - StringTemplateResourceLoader:字符串模板加载器,用于加载字符串模板,如本例所示 - FileResourceLoader:文件模板加载器,需要一个根目录作为参数构造,传入getTemplate方法的String是模板文件相对于Root目录的相对路径 - ClasspathResourceLoader:现代web应用最常用的文件模板加载器,模板文件位于Classpath里 - WebAppResourceLoader:用于webapp集成,假定模板根目录就是WebRoot目录,参考web集成章 - MapResourceLoader : 可以动态存入模板 - CompositeResourceLoader 混合使用多种加载方式 代码第5行将变量name传入模板里,其值是“Beetl”。 代码第6行是渲染模板,得到输出,template提供了多种获得渲染输出的方法,如下 - template.render() 返回渲染结果,如本例所示 - template.renderTo(Writer) 渲染结果输出到Writer里,如果你的Writer是一个FilterWriter,则可把输出保存到文件里 - template.renderTo(OutputStream ) 渲染结果输出到OutputStream里 > 1. Beetl 支持为模板自定义定界符和占位符,如本例子采用的默认占位符号${}。 后面可以看到,可以定义任意其他符号,比如#{},或者## > 3. 如果不想写代码直接体验Beetl提供的基本功能,可以使用http://ibeetl.com/beetlonline/ #### 模板基础配置 Beetl提供不但功能齐全,而且还有很多独特功能,通过简单的配置文件,就可以定义众多的功能,默认情况下,Configuration类总是会先加载默认的配置文件(位于/org/beetl/core/beetl-default.properties,作为新手,**通常只需要关注3,4,5,6行定界符的配置,以及12行模板字符集的配置就可以了**,其他配置会在后面章节陆续提到,同时,对于Spring等框架,有些配置将会被这些框架的配置覆盖,需要参考后面章节)下,beetl-default.properties其内容片断如下: ```properties #默认配置 ENGINE=org.beetl.core.engine.FastRutimeEngine DELIMITER_PLACEHOLDER_START=${ DELIMITER_PLACEHOLDER_END=} DELIMITER_STATEMENT_START=<% DELIMITER_STATEMENT_END=%> DIRECT_BYTE_OUTPUT = FALSE HTML_TAG_SUPPORT = true HTML_TAG_FLAG = # HTML_TAG_BINDING_ATTRIBUTE = var NATIVE_CALL = TRUE TEMPLATE_CHARSET = UTF-8 ERROR_HANDLER = org.beetl.core.ConsoleErrorHandler NATIVE_SECUARTY_MANAGER= org.beetl.core.DefaultNativeSecurityManager MVC_STRICT = FALSE #资源配置,resource后的属性只限于特定ResourceLoader RESOURCE_LOADER=org.beetl.core.resource.ClasspathResourceLoader #classpath 根路径 RESOURCE.root= / #是否检测文件变化,开发用true合适,但线上要改为false RESOURCE.autoCheck= true #自定义脚本方法文件的Root目录和后缀 RESOURCE.functionRoot = functions RESOURCE.functionSuffix = html #自定义标签文件Root目录和后缀 RESOURCE.tagRoot = htmltag RESOURCE.tagSuffix = tag ##### 扩展 ############## ## 内置的方法 FN.date = org.beetl.ext.fn.DateFunction ...... ##内置的功能包 FNP.strutil = org.beetl.ext.fn.StringUtil ...... ##内置的默认格式化函数 FTC.java.util.Date = org.beetl.ext.format.DateFormat ..... ## 标签类 TAG.include= org.beetl.ext.tag.IncludeTag TAG.html.include= org.beetl.ext.tag.html.IncludeResourceHtmlTag TAG.html.foreach= org.beetl.ext.tag.html.ForeachHtmlTag ``` 这配置文件整体说明了Beetl提供的功能 第2行配置引擎实现类,默认即可. 第3,4行指定了占位符号,默认是`${` `}`,也可以指定为其他占位符。 第5,6行指定了语句的定界符号,默认是`<% ` ` %>`,也可以指定为其他定界符号 第7行指定IO输出模式,默认是FALSE,即通常的字符输出,在考虑高性能情况下,可以设置成`true`。详细请参考高级用法 第8,9行指定了支持HTML标签,且符号为#,默认配置下,模板引擎识别`<#tag ></#tag>`这样的类似html标签,并能调用相应的标签函数或者模板文件。你也可以指定别的符号,如**bg:** 则识别`<bg:` 第10行 指定如果标签属性有`var`,则认为是需要绑定变量给模板的标签函数 第11行指定允许本地Class直接调用 第12行指定模板字符集是**UTF-8** 第13行指定异常的处理类,默认是**ConsoleErrorHandler**,他将在render发生异常的时候在后台打印出错误信息(`System.out`)。 第14行指定了本地Class调用的安全策略 第15行配置了是否进行严格MVC,通常情况下,此处设置为false. 第18行指定了默认使用的模板资源加载器,注意,在beetl与其他MVC框架集成的时候,模板加载器不一定根据这个配置,比如spring,他的RESOURCE_LOADER以spring的配置为准 第20到22行配置了模板资源加载器的一些属性,如设置根路径为/,即Classpath的顶级路径,并且总是检测模板是否更改 第23行配置了自定义的方法所在的目录以及文件名后缀。beetl既支持通过java类定义方法,也支持通过模板文件来定义方法 第26行配置了自定义的html标签所在的目录以及文件名后缀。beetl既支持通过java类定义标签,也支持通过模板文件来定义标签 第31行注册了一个`date`方法,其实现类是`org.beetl.ext.fn.DateFunction` 第34行注册了一个方法包`strutil`,其实现类`org.beetl.ext.fn.StringUtil`,此类的每个`public`方法都将注册为beetl的方法 第37行注册了一个日期格式化函数 第40行注册了一个`include`标签函数 模板开发者不需要关心如上配置,可以创建一个beetl.properties的配置文件,此时,该配置文件将覆盖默认的配置文件属性,比如,你的定界符考虑是`<!--:` 和 `-->` ,则在**beetl.properties**加入一行即可,并将此配置文件放入Classpath根目录下即可。 Configuration.defaultConfiguration()总是先加载系统默认的,然后再加载Beetl.properties的配置属性,如果有重复,用后者代替前者的配置 ```properties #自定义配置 DELIMITER_STATEMENT_START=<!--: DELIMITER_STATEMENT_END=--> DELIMITER_PLACEHOLDER_START=#{ DELIMITER_PLACEHOLDER_END=} ``` 3.0 以后版本支持第二对占位符,定界符 ```properties DELIMITER_STATEMENT_START2=@ DELIMITER_STATEMENT_END2= DELIMITER_PLACEHOLDER_START2=${ DELIMITER_PLACEHOLDER_END2=} ``` #### 模板资源加载器 资源加载器是根据String值获取Resource实例的工场类,同时资源加载器还要负责响应模板引擎询问模板是否变化的调用。对于新手来说,无需考虑模板资源加载器如何实现,只需要根据自己场景选择系统提供的三类模板资源加载器即可 ##### 字符串模板加载器 在创建GroupTemplate过程中,如果传入的是StringTemplateResourceLoader,则允许通过调用gt.getTemplate(String template)来获取模板实例对象,如2.1所示 ##### 文件资源模板加载器 更通常情况下,模板资源是以文件形式管理的,集中放在某一个文件目录下(如webapp的模板根目录就可能是WEB-INF/template里),因此,可以使用FileResourceLoader来加载模板实例,如下代码: ```java String root = System.getProperty("user.dir")+File.separator+"template"; FileResourceLoader resourceLoader = new FileResourceLoader(root,"utf-8"); Configuration cfg = Configuration.defaultConfiguration(); GroupTemplate gt = new GroupTemplate(resourceLoader, cfg); Template t = gt.getTemplate("/s01/hello.txt"); String str = t.render(); System.out.println(str); ``` 第1行代码指定了模板根目录,即位于项目工程下的template目录 第2行构造了一个资源加载器,并指定字符集为UTF-8 (也可不指定,因为配置文件默认就是UTF-8); 第5行通过模板的相对路径/s01/hello.txt来加载模板 ##### Classpath资源模板加载器 最常用的加载器。在springboot里,模板资源是打包到jar文件或者同Class放在一起,因此,可以使用ClasspathResourceLoader来加载模板实例,如下代码: ```java ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader("org/beetl/sample/s01/"); Configuration cfg = Configuration.defaultConfiguration(); GroupTemplate gt = new GroupTemplate(resourceLoader, cfg); Template t = gt.getTemplate("/hello.txt"); String str = t.render(); System.out.println(str); ``` 第1行代码指定了模板根目录,即搜索模板的时候从根目录开始,如果new ClasspathResourceLoader("template/"),则表示搜索template下的模板。此处用空构造函数,表示搜索路径是根路径,且字符集默认字符集UTF-8. 第4行通过模板的相对路径org/beetl/sample/s01/hello.txt来加载模板 ##### WebApp资源模板加载器 WebAppResourceLoader 是用于Java EE web应用的资源模板加载器,默认根路径是WebRoot目录。也可以通过制定root属性来设置相对于WebRoot的的模板根路径,从安全角考虑,建议放到WEB-INF目录下 如下是Jfinal集成 里初始化GroupTemplate的方法 ```java Configuration cfg = Configuration.defaultConfiguration(); WebAppResourceLoader resourceLoader = new WebAppResourceLoader(); groupTemplate = new GroupTemplate(resourceLoader, cfg); ``` WebAppResourceLoader 假定 beetl.jar 是位于 WEB-INF/lib 目录下,因此,可以通过WebAppResourceLoader类的路径来推断出WebRoot路径从而指定模板根路径。所有线上环境一般都是如此,如果是开发环境或者其他环境不符合此假设,你需要调用resourceLoader.setRoot() 来指定模板更路径 ##### 自定义资源模板加载器 有时候模板可能来自文件系统不同目录,或者模板一部分来自某个文件系统,另外一部分来自数据库,还有的情况模板可能是加密混淆的模板,此时需要自定义资源加载,继承ResouceLoader才能实现模板功能,这部分请参考高级部分 #### 定界符与占位符号 Beetl模板语言类似JS语言和习俗,只需要将Beetl语言放入定界符号里即可,如默认的是<% %> ,占位符用于静态文本里嵌入占位符用于输出,如下是正确例子 ```javascript <% var a = 2; var b = 3; var result = a+b; %> hello 2+3=${result} ``` **千万不要**在定界符里使用占位符号,因为占位符仅仅嵌在静态文本里,如下例子是**错误**例子 ```javascript <% var a = "hi"; var c = ${a}+"beetl"; //应该是var c = a+"beetl" %> ``` 定界符和占位符 通常还有别的选择,如下定界符 - @ 和回车换行 (此时,模板配置DELIMITER_STATEMENT_END= 或者 DELIMITER_STATEMENT_END=null 都可以) - \#: 和回车换行 - &lt;!\-\-: 和 \-\-&gt; - &lt;!\-\-# 和 \-\-&gt; - &lt;? 和 ?&gt; 占位符\-\-#{ }\-\#\# 你也可以与团队达成一致意见来选择团队喜爱的定界符号和占位符号。 定界符号里是表达式,如果表达式跟定界符或者占位符有冲突,可以在用 “\” 符号,如 ```javascript @for(user in users){ email is ${user.name}\@163.com @} ${[1,2,3]} //输出一个json列表 ${ {key:1,value:2 \} } //输出一个json map,} 需要加上\ ``` #### 注释 Beetl语法类似js语法,所以注释上也同js一样: 单行注释采用// 多行注视采用/**/ ````javascript <% /*此处是一个定义变量*/ var a = 3; //定义一个变量. /* 以下内容都将被注释 %> <% */ %> ```` 第2行是一个多行注释 第3行是一个单行注释 第5行到第8行采用的是多行注释,因此里面有内容也是注释,模板将不予处理 #### 临时变量定义 在模板中定义的变量成为临时变量,这类似js中采用var 定义的变量,如下例子 ```javascript <% var a = 3; var b = 3,c = "abc",d=true,e=null; var f = [1,2,3]; var g = {key1:a,key2:c}; var i = a+b; %> ``` 模板里定义的临时变量只能在当前模板中使用,如果想在子模板里使用,需要显示当着参数传递 #### 全局变量定义 全局变量是通过在java代码里调用template.binding传入的变量,这些变量能在模板的任何一个地方,包括子模板都能访问到。如java代码里 ```javascript template.binding("list",service.getUserList()); //在模板里 <% for(user in list){ %> hello,${user.name}; <% } %> ``` 自从2.8.0版本后,有一个特殊的变量成为root变量,当模板找不到变量的时候,会寻找root变量的属性来作为变量的值,这个root变量必须绑定为"_root" ```javascript template.binding("_root",new User()); //在模板里,俩个都一样 ${name} ${wife.name} ``` 这里name 和 wife都是User对象的属性 #### 共享变量 共享变量指在所有模板中都可以引用的变量,可通过groupTemplate.setSharedVars(Map<String, Object> sharedVars)传入变量,这些变量能用在 **所有模板** 的任何一个地方 ```java //..... GroupTemplate gt = new GroupTemplate(resourceLoader, cfg); // 设置共享变量 Map<String,Object> shared = new HashMap<String,Object>(); shared.put("name", "beetl"); gt.setSharedVars(shared); Template t = gt.getTemplate("/org/beetl/sample/s0208/t1.txt"); String str = t.render(); System.out.println(str); t = gt.getTemplate("/org/beetl/sample/s0208/t2.txt"); str = t.render(); System.out.println(str); ``` ```javascript //t1.txt hi,${name} //t2.txt hello,${name} ``` #### 模板变量 模板变量是一种特殊的变量,即可以将模板中任何一段的输出赋值到该变量,并允许稍后在其他地方使用,如下代码 ```javascript <% var content = { var c = "1234"; print(c); %> 模板其他内容: <% }; %> ``` 第2行定义了一个模板变量content = { …} ; 此变量跟临时变量一样,可以在其他地方使用,最常见的用法是用于复杂的布局。请参考高级用法布局 #### 引用属性 属性引用是模板中的重要一部分,beetl支持属性同javascript的支持方式一样,如下 * Beetl支持通过”.”号来访问对象的的属性,如果javascript一样。如果User对象有个getName()方法,那么在模板中,可以通过${xxx.name}来访问 * 如果模板变量是数组或者List类,这可以通过[] 来访问,如${userList[0]} * 如果模板变量是Map类,这可以通过[]来访问,如${map[“name”]},如果key值是字符串类型,也可以使用${map.name}.但不建议这么使用,因为会让模板阅读者误以为是一个Pojo对象 * Beetl也支持Generic Get方式,即如果对象有一个public Object get(String key)方法,可以通过”.”号或者[]来访问,譬如 `${activityRecord.name}`或者`${activityRecord[“name”] }`都将调用activityRecord的 get(String key)方法。如果对象既有具体属性,又有Generic get(这种模型设计方式是不值得鼓励),则以具体属性优先级高. * Beetl也可以通过[]来引用属性,如`${user[“name”]}` 相当于`${user.name}`.这跟javascript保持一致。但建议不这么做,因为容易让阅读模板的人误认为这是一个Map类型.但好处是可以动态引用属性,比如; ```java var attrName="age"; var age=user[attrName] ``` * 需要知道Java集合,数组长度,统一用虚拟属性`~size`来表示 ```java var list=[1,2,3]; var size = list.~size ``` * 虚拟属性:Beetl 还可以定义额外的对象属性,而无需更改java对象,这叫着虚拟属性,如,对于所有集合,数组,都有共同的虚拟属性size.虚拟属性是“.~”+虚拟属性名 ```javascript template.binding("list",service.getUserList()); template.binding("pageMap",service.getPage()); //在模板里 总共 ${list.~size} <% for(user in list){ %> hello,${user.name}; <% } %> 当前页${pageMap['page']},总共${pageMap["total"]} ``` #### 属性赋值 Beetl2.7.0 开始支持对象赋值,如: ```javascript <% var user = .... user.name="joelli"; user.friends[0] = getNewUser(); user.map["name"] = "joelli"; %> ``` 赋值很少用于模板引擎,在规则引擎,脚本引擎使用场景倒是可以修改对象属性。Beetl支持语法定制,可以根据你的使用场景禁止对象赋值。 #### 算数表达式 Beetl支持类似javascript的算术表达式和条件表达式,如+ - * / % 以及(),以及自增++,自减-- ```javascript <% var a = 1; var b = "hi"; var c = a++; var d = a+100.232; var e = (d+12)*a; var f = 122228833330322.1112h %> ``` Beetl里定义的临时变量类型默认对应的java类型是Int型或者double类型,对于模板常用情况,已经够了.如果需要定义长精度类型(对应java的BigDecimal),则需要在数字末尾加上h以表示这是长精度BigDecimal,其后的计算和输出以及逻辑表达式都将按照长精度类型来考虑。 #### 逻辑表达式 Beetl支持类似Javascript,java的条件表达式 如\>,\<,==,!=,\>= , \<= 以及 !, 还有&&和 || ,还有三元表达式等,如下例子 ```javascript <% var a = 1; var b="good"; var c = null; if(a!=1&&b=="good"&&c==null){ ...... } %> ``` 三元表达式如果只考虑true条件对应的值的话,可以做简化,如下俩行效果是一样的。 ```javascript <% var a = 1 ; %> ${a==1?"ok":''} ${a==1?"ok"} ``` #### 循环语句 Beetl支持丰富的循环方式,如for-in,for(exp;exp;exp),以及while循环,以及循环控制语句break;continue; 另外,如果没有进入for循环体,还可以执行elsefor指定的语句。 ##### for-in for-in循环支持遍历集合对象,对于List和数组来说以及Iterator,对象就是集合对象,对于Map来说,对象就是Map.entry,如下俩个例子 ```javascript <% for(user in userList){ print(userLP.index); print(user.name); } %> ``` 第三行代码userLP是Beetl隐含定义的变量,是一个ILoopStatus实例,能在循环体内使用。其命名规范是item名称后加上LP,他提供了当前循环的信息,如 - **userLP.index **当前的索引,从1开始 - userLP.dataIndex 索引,从0开始 - **userLP.size **集合的长度 - **userLP.first** 是否是第一个 - **userLP.last** 是否是最后一个 - **userLP.even** 索引是否是偶数 - **userLP.odd** 索引是否是奇数 > 如何记住后缀是LP,有俩个诀窍,英语棒的是Loop的缩写,拼音好的是老婆的拼音缩写,这可以让程序员每次写到这的时候都会想想老婆(不管有没有,哈哈) 如下是Map使用例子 ```javascript <% for(entry in map){ var key = entry.key; var value = entry.value; print(value.name); } %> ``` ##### for(exp;exp;exp) 对于渲染逻辑更为常见的是经典的for循环语句,如下例子 ```javascript <% var a = [1,2,3]; for(var i=0;i<a.~size;i++){ print(a[i]); } %> ``` ##### while 对于渲染逻辑同样常见的有的while循环语句,如下例子 ```javascript <% var i = 0; while(i<5){ print(i); i++; } %> ``` ##### elsefor 不同于通常程序语言,如果没有进入循环体,则不需额外的处理,模板渲染逻辑更常见情况是如果没有进入循环体,还需要做点什么,因此,对于for循环来说,还有elsefor 用来表达如果循环体没有进入,则执行elsefor 后的语句 ```javascript <% var list = []; for(item in list){ }elsefor{ print("未有记录"); } %> ``` #### 条件语句 ##### if else 同js一样,支持if else,如下例子 ```javascript <% var a =true; var b = 1; if(a&&b==1){ }else if(a){ }else{ } %> ``` ##### switch-case 同js一样,支持switch-case,如下例子 ```javascript <% var b = 1; switch(b){ case 0: print("it's 0"); break; case 1: print("it's 1"); break; default: print("error"); } %> ``` > switch变量可以支持任何类型,而不像js那样只能是整形 ##### select-case select-case 是switch case的增强版。他允许case 里有逻辑表达式,同时,也不需要每个case都break一下,默认遇到合乎条件的case执行后就退出。 ```javascript <% var b = 1; select(b){ case 0,1: print("it's small int"); case 2,3: print("it's big int"); default: print("error"); } %> ``` select 后也不需要一个变量,这样case 后的逻辑表达式将决定执行哪个case.其格式是 ```javascript <% select { case boolExp,orBoolExp2: doSomething(); } %> ``` ```javascript <% var b = 1; select{ case b<1,b>10: print("it's out of range"); break; case b==1: print("it's 1"); break; default: print("error"); } %> ``` #### try-catch 通常模板渲染逻辑很少用到try-catch 但考虑到渲染逻辑复杂性,以及模板也有不可控的地方,所以提供try catch,在渲染失败的时候仍然能保证输出正常 ```javascript <% try{ callOtherSystemView() }catch(error){ print("暂时无数据"); } %> ``` error代表了一个异常,你可以通过error.message 来获取可能的错误信息 也可以省略catch部分,这样出现异常,不做任何操作 #### 虚拟属性 虚拟属性也是对象的属性,是虚拟的,非模型对象的真实属性,这样的好处是当模板需要额外的用于显示的属性的时候但又不想更改模型,便可以采用这种办法 如beetl内置的虚拟属性.~size 针对了数组以及集合类型。 ```javascript ${user.gender} ${user.~genderShowName} ``` ~genderShowName 是虚拟属性,其内部实现根据boolean变量gender来显示性别 如何完成虚拟属性,请参考高级用法 #### 函数调用 Beetl内置了少量实用函数,可以在Beetl任何地方调用。如下例子是调用date 函数,不传参数情况下,返回当前日期 ```javascript <% var date = date(); var len = strutil.length("cbd"); println("len="+len); %> ``` 注意函数名支持namespace方式,因此代码第3行调用的函数是strutil.length 定义beetl的方法非常容易,有三种方法 - 实现Function类的call方法,并添加到配置文件里,或者显示的通过代码注册registerFunction(name,yourFunction) - 可以直接调用registerFunctionPackage(namespace,yourJavaObject),这时候yourJavaObject里的所有public方法都将注册为Beetl方法,方法名是namespace+"."+方法名 - 可以直接写模板文件并且以html作为后缀,放到root/functions目录下,这样此模板文件自动注册为一个函数,其函数名是该模板文件名。这种方法很少人用 详情请参考高级用法 Beetl内置函数请参考附录,以下列出了常用的函数(可以从源码org.beetl.ext.tag.fn下找到其实现Function) - **date** 返回一个java.util.Date类型的变量,如 date() 返回一个当前时间(对应java的java.util.Date); ${date( "2011-1-1" , "yyyy-MM-dd" )} 返回指定日期,date(ms),指定一个毫秒数。相当于调用java.util.Date(ms) - **print** 打印一个对象 print(user.name); - **println** 打印一个对象以及回车换行符号,回车换号符号使用的是模板本身的,而不是本地系统的.如果仅仅打印一个换行符,则直接调用println() 即可 - **nvl** 函数nvl,如果对象为null,则返回第二个参数,否则,返回自己 nvl(user,"不存在") - **isEmpty** 判断变量或者表达式是否为空,变量不存在,变量为null,变量是空字符串,变量是空集合,变量是空数组,此函数都将返回true - **isNotEmpty** 同上,判断对象是否不为空 - **has** 变量名为参数,判断是否存在此"全局变量",如 has(userList),类似于1.x版本的exist("userList"),但不需要输入引号了.注意,has和isEmpety 判断的是从java传到模板的全局变量,而不是临时变量 - **hasAttrbiute** 测试目标对象是否有此属性,hasAttribute(user,"name") - **assert** 如果表达式为false,则抛出异常 - trim 截取数字或者日期,返回字符,如trim(12.456,2)返回"12.45",trim(date,'yyyy')返回"2017" - **trunc** 截取数字,保留指定的小数位,如trunc(12.456,2) 输出是12.45.不推荐使用,因为处理float有问题,兼容原因保留了 - **decode** 一个简化的if else 结构,如 decode(a,1,"a=1",2,"a=2","不知道了"),如果a是1,这decode输出"a=1",如果a是2,则输出"a==2", 如果是其他值,则输出"不知道了" - **debug** 在控制台输出debug指定的对象以及所在模板文件以及模板中的行数,如debug(1),则输出1 [在3行@/org/beetl/core/lab/hello.txt],也可以输出多个,如debug("hi",a),则输出hi,a=123,[在3行@/org/beetl/core/lab/hello.txt] - **parseInt** 将数字或者字符解析为整形 如 parseInt("123"); - **parseLong** 将数字或者字符解析为长整形,parseInt(123.12); - **parseDouble** 将数字或者字符解析为浮点类型 如parseDouble("1.23") - **range** 接收三个参数,初始值,结束值,还有步增(可以不需要,则默认为1),返回一个Iterator,常用于循环中,如for(var i in range(1,5)) {print(i)},将依次打印1234. - **flush** 强制io输出。 - **json**,将对象转成json字符串,如 var data = json(userList) 可以跟一个序列化规则 如,var data = json(userList,"[*].id:i"),具体参考 [https://git.oschina.net/xiandafu/beetl-json](https://git.oschina.net/xiandafu/beetl-json) - **pageCtx** ,仅仅在web开发中,设置一个变量,然后可以在页面渲染过程中,调用此api获取,如pageCtx("title","用户添加页面"),在其后任何地方,可以pageCtx("title") 获取该变量 - **type.new** 创建一个对象实例,如 var user = type.new("com.xx.User"); 如果配置了IMPORT_PACKAGE,则可以省略包名,type.new("User") - **type.name** 返回一个实例的名字,var userClassName = type.name(user),返回"User" - **global** 返回一个全局变量值,参数是一个字符串,如 var user = global("user_"+i); - **cookie** 返回指定的cookie对象 ,如var userCook = cookie("user"),allCookies = cookie(); #### 安全输出(重要) 安全输出是任何一个模板引擎必须重视的问题,否则,将极大困扰模板开发者。Beetl中,如果要输出的模板变量为null,则beetl将不做输出,这点不同于JSP,JSP输出null,也不同于Freemarker,如果没有用!,它会报错. 模板中还有俩种情况会导致模板输出异常 - 有时候模板变量并不存在,这时候必须报错,如果简单忽略不输出(Velocity就这样),很容易留坑 - 模板变量为null,但输出的是此变量的一个属性,如${user.wife.name} 针对前俩种情况,可以在变量引用后加上!以提醒beetl这是一个安全输出的变量,变量确实有可能不存在 如${user.wife.name! },即使user不存在,或者user为null,或者user.wife为null,或者user.wife.name为null beetl都不将输出 可以在!后增加一个常量(字符串,数字类型等),或者另外一个变量,方法,本地调用,作为默认输出,譬如: ${user.wife.name!”单身”},如果user为null,或者user.wife为null,或者user.wife.name为null,输出”单身” 譬如 ${user.birthday!@System.constants.DefaultBir}, 表示如果user为null,或者user. birthday为null,输出System.constants.DefaultBir 还有一种情况很少发生,但也有可能,输出模板变量发生的任何异常,如变量内部抛出的一个异常 这需要使用格式${!(变量)},这样,在变量引用发生任何异常情况下,都不作输出,譬如 ${!(user.name)},,beetl将会调用user.getName()方法,如果发生异常,beetl将会忽略此异常,继续渲染 值得注意的是,在变量后加上!不仅仅可以应用于占位符输出(但主要是应用于占位符输出),也可以用于表达式中,如: ```javascript <% var k = user.name!'N/A'+user.age!; %> <% ${k} %> ``` 如果user为null,则k值将为N/A 在有些模板里,可能整个模板都需要安全输出,也可能模板的部分需要安全输出,使用者不必为每一个表达式使用!,可以使用beetl的安全指示符号来完成安全输出 如: ```javascript <% DIRECTIVE SAFE_OUTPUT_OPEN; %> ${user.wife.name} 模板其他内容,均能安全输出…… <% //关闭安全输出。 DIRECTIVE SAFE_OUTPUT_CLOSE; %> ``` Beetl不建议每一个页面都使用DIRECTIVE SAFE_OUTPUT_OPEN,这样,如果真有不期望的错误,不容易及时发现,其次,安全输出意味着beetl会有额外的代码检测值是否存在或者是否为null,性能会略差点。所以建议及时关闭安全输出(这不是必须的,但页面所有地方是安全输出,可能不容易发现错误) 如果你的所有模板都想安全输出,可以配置,但不推荐。严格了错误,就像try catch吃掉异常一样不容易发现这是个错误 ``` SAFE_OUTPUT=true ``` 在for-in 循环中 ,也可以为集合变量增加安全输出指示符号,这样,如果集合变量为null,也可以不进入循环体,如: ```javascript <% var list = null; for(item in list!){ }elsefor{ print("no data"); } %> ``` ##### 变量是否存在 ```javascript <% if(has(flag)){ print("flag变量存在,可以访问") } %> ``` 如果需要判断变量是否存在,如果存在,还有其他判断条件,通常都这么写 ```javascript <% if(has(flag)&&flag==0){ //code } %> ``` 如果flag存在,而且值是0,都将执行if语句 但是,有更为简便的方法是直接用安全输出,如 ```javascript <% if(flag!0==0){ //code } %> ``` flag!0 取值是这样的,如果flag不存在,则为0,如果存在,则取值flag的值,类似三元表达式 if((has(flag)?flag:0)==0) ##### 安全输出表达式 安全输出表达式可以包括 - 字符串常量,如 ${user.count!"无结果"} - boolean常量 ${user.count!false} - 数字常量,仅限于正数,因为如果是负数,则类似减号,容易误用,因此,如果需要表示负数,请用括号,如${user.count!(-1)} - class直接调用,如${user.count!@User.DEFAULT_NUM} - 方法调用,如 ${user.count!getDefault() } - 属性引用,如 ${user.count!user.maxCount } - 任何表达式,需要用括号 #### 输出格式化 几乎所有的模板语言都支持格式化,Beetl也不列外,如下例子Beetl提供的内置日期格式 ```javascript <% var date = date(); %> Today is ${date,dateFormat="yyyy-MM-dd"}. Today is ${date,dateFormat} salary is ${salary,numberFormat="##.##"} ``` 格式化函数只需要一个字符串作为参数放在=号后面,如果没有为格式化函数输入参数,则使用默认值,dateFormat格式化函数默认值是local Beetl也允许为指定的java class设定格式化函数,譬如已经内置了对java.util.Date,java.sql.Date 设置了了格式化函数,因此上面的例子可以简化为 ```javascript ${date,“yyyy-MM-dd”} ``` Beetl针对日期和数字类型提供的默认的格式化函数,在org/beetl/core/beetl-default.properties里,注册了 ```properties ##内置的格式化函数 FT.dateFormat = org.beetl.ext.format.DateFormat FT.numberFormat = org.beetl.ext.format.NumberFormat ##内置的默认格式化函数 FTC.java.util.Date = org.beetl.ext.format.DateFormat FTC.java.sql.Date = org.beetl.ext.format.DateFormat FTC.java.sql.Time = org.beetl.ext.format.DateFormat FTC.java.sql.Timestamp = org.beetl.ext.format.DateFormat FTC.java.lang.Short = org.beetl.ext.format.NumberFormat FTC.java.lang.Long = org.beetl.ext.format.NumberFormat FTC.java.lang.Integer = org.beetl.ext.format.NumberFormat FTC.java.lang.Float = org.beetl.ext.format.NumberFormat FTC.java.lang.Double = org.beetl.ext.format.NumberFormat FTC.java.math.BigInteger = org.beetl.ext.format.NumberFormat FTC.java.math.BigDecimal = org.beetl.ext.format.NumberFormat FTC.java.util.concurrent.atomic.AtomicLong = org.beetl.ext.format.NumberFormat FTC.java.util.concurrent.atomic.AtomicInteger = org.beetl.ext.format.NumberFormat ``` #### 标签函数 所谓标签函数,即允许处理模板文件里的一块内容,功能等于同jsp tag。如Beetl内置的layout标签 index.html ```javascript <% layout("/inc/layout.html",{title:'主题'}){ %> Hello,this is main part <% } %> ``` layout.html ```javascript title is ${title} body content ${layoutContent} footer ``` 第1行变量title来自于layout标签函数的参数 第2行layoutContent 是layout标签体{}渲染后的结果 关于layout标签,参考高级主题布局 Beetl内置了另外一个标签是include,允许 include 另外一个模板文件 ```javascript <% include("/inc/header.html"){} %> ``` 在标签中,{} 内容将依据标签的实现而执行,layout标签将执行{}中的内容,而include标签则忽略标签体内容。 关于如何实现标签函数,请参考高级主题,如下是一个简单的的标签函数: ```java public class CompressTag extends Tag{ @Override public void render(){ BodyContent content = getBodyContent(); String content = content.getBody(); String zip = compress(conent); ctx.byteWriter.write(zip); } } ``` include, includeUrl,includeJSP,还有includeFragment(包含模板中的某一部分)都是Beetl提供的include系列标签函数,includeUrl,includeJSP考虑到需要WEB环境,并没有内置,需要手工注册,参考IncludeJSPTag.java,IncludeUrlTag.java #### HTML标签 Beetl 也支持HTML tag形式的标签(但实质还是标签函数), 区分beetl的html tag 与 标准html tag。如设定`HTML_TAG_FLAG=#`,则如下html tag将被beetl解析 ```xml <#footer style=”simple”/> <#richeditor id=”rid” path="${ctxPath}/upload" name=”rname” maxlength=”${maxlength}”> ${html} …其他模板内容 </#richdeitor> <#html:input id=’aaaa’ /> ``` 也可以设置别的符号,比如`HTML_TAG_FLAG=my:`,那么如上标签则应该写成 ```java <my:footer style=”simple”/> <my:richeditor id=”rid” path="${ctxPath}/upload" name=”rname” maxlength=”${maxlength}”> ${html} …其他模板内容 </my:richdeitor> <my:html:input id=’aaaa’ /> ``` 如对于标签footer,Beetl默认会寻找WebRoot/htmltag/footer.tag(可以通过配置文件修改路径和后缀) ,内容如下: ```javascript <% if(style==’simple’){ %> 请联系我 ${session.user.name} <% }else{ %> 请联系我 ${session.user.name},phone:${session.user.phone} <% } %> ``` 如下还包含了自定义html标签一些规则 - 可以在自定义标签里引用标签体的内容,标签体可以是普通文本,beetl模板,以及嵌套的自定义标签等。如上<#richeditor 标签体里,可用“tagBody”来引用 - HTML自定义标签 的属性值均为字符串 如<#input value="123" />,在input.tag文件里 变量value的类型是字符串 - 可以在属性标签里引用beetl变量,如<#input value="${user.age}" />,此时在input.tag里,value的类型取决于user.age - 在属性里引用beetl变量,不支持格式化,如<#input value="${user.date,'yyyy-MM-dd'}"/>,如果需要格式化,需要在input.tag文件里自行格式化 - 在标签属性里传json变量需要谨慎,因为json包含了"}",容易与占位符混合导致解析出错,因此得使用"\\"符号,如<#input value="${ {*age*:25\\} }" /> - html tag 属性名将作为 其对应模板的变量名。如果属性名包含“-”,则将转为驼峰命名的变量,如data-name,转为dataName,这个是通过AttributeNameConvert类实现的,也可以实现其他转化规则 - 默认机制下,HTMLTagSupportWrapper2 实现了标签(2.8.x以前使用HTMLTagSupportWrapper) - 标签属性带有"-“符号,会自动去掉,后面的字母大写,以符合变量命名规范 - 所有的标签属性都保存在"$cols" 变量里,这是一个map,key为属性转化后的变量名,value是属性名(参考下面例子) 如果采用模板来写html标签功能不够强大,beetl支持写标签函数(参考上一节)来实现html标签,标签函数args[0]表示标签名,这通常没有什么用处,args[1] 则是标签的属性,参数是个map,key是html tag的属性,value是其属性值,如下用java完成的html 标签用于输出属性值 ```java public class SimpleHtmlTag extends Tag{ @Override public void render(){ String tagName = (String) this.args[0]; Map attrs = (Map) args[1]; String value = (String) attrs.get("attr"); //Map allColsDefine = (map)attrs.get("$cols"); try{ this.ctx.byteWriter.writeString(value); }catch (IOException e){ } } } ``` 如果注册gt.registerTag("simpleTag", SimpleHtmlTag.class); 则如下模板输出了attr属性值abc ```xml <#simpleTag attr="abc"></#simpleTag> ``` > HTML_TAG_FLAG默认为#用来区别是否是beetl的html tag,你也可以设置成其他符号,比如 "my:",这样,\<my:table\>\</my:table\> 其实是一个指向table.tag的标签实现 Beetl默认标签并不包含父子关系,如果想在标签传递参数,可以使用pageCtx函数 ~~~html <#form> <#input name="user"></#input> </#form> ~~~ form标签实现如下 ~~~html <% var a = pageCtx("formParas",{"user":"abc"}); %> <form> ${tagBody} </form> ~~~ input标签可以获取formParas参数 ~~~html <% var all = pageCtx("formParas"); var value = all[name]; %> <input name="${name}" value=${value} /> ~~~ #### 绑定变量的HTML标签 对于html标签(参考上一节),Beetl还 支持将标签实现类(java代码)里的对象作为临时变量,被标签体引用。此时需要实现GeneralVarTagBinding (此类是Tag的子类) 该类提供另外3个方法 - void binds(Object… array) 子类在render方法里调用此类以实现变量绑定,绑定顺序同在模板中声明的顺序 - void bind(String name, Object value) 子类在render方法里调用此类以实现变量绑定,name是模板中声明的变量名,用此方法绑定不如binds更灵活,不再推荐 - Object getAttributeValue 获得标签的属性 - Map getAttributes 获得标签的所有属性 ```java public class TagSample extends GeneralVarTagBinding{ @Override public void render(){ int limit = Integer.parseInt((String) this.getAttributeValue("limit")); for (int i = 0; i < limit; i++){ this.binds(i) this.doBodyRender(); } } } //在某处注册一下标签TagSample //gt.registerTag("tag", TagSample.class); ``` 如上例子,render方法将循环渲染标签体limit次,且每次都将value赋值为i。我们再看看模板如何写的 ```xml <#tag limit="3" var="value"> ${value} </#tag> ``` 类似于常规html标签,增加了一个var属性(可以通过配置文件改成其他属性名)。,后跟上要绑定的变量列表,如上例只绑定了一个value变量,如果需要绑定多个变量,则用逗号分开,如var1,var2 上 注意,由于标签使用因为太长可能换行或者是文本格式化导致换行,目前beetl只允许在属性之间换行,否则,将报标签解析错误。 通过var定义的变量只能在标签体中使用,如果想定义一个能在标签体外能使用的变量,需要使用export ~~~java <#define limit="3" expor="value"/> ${value} ~~~ 如果想要在整个模板都使用,可以使用$export ~~~java <% if(true){ %> <#define limit="3" $expor="value"/> <% } %> ${value} ~~~ > var 和 export 这个属性名代表了特殊意义,用于申明变量。也可以通过配置使用其他属性名字 > > ``` > HTML_TAG_BINDING_ATTRIBUTE = var,export > ``` #### 直接调用java方法和属性 可以通过符号@来表明后面表达式调用是java风格,可以调用对象的方法,属性 ```javascript ${@user.getMaxFriend(“lucy”)} ${@user.maxFriend[0].getName()} ${@com.xxxx.constants.Order.getMaxNum()} ${@com.xxxx.User$Gender.MAN} <% var max = @com.xxxx.constants.Order.MAX_NUM; var c =1; var d = @user.getWife(c).getName(); %> ``` 可以调用instance的public方法和属性,也可以调用静态类的属性和方法 ,需要加一个 @指示此调用是直接调用class,其后的表达式是java风格的。 - GroupTemplate可以配置为不允许直接调用Class以增强安全性,具体请参考配置文件. - 也可以通过安全管理器配置到底哪些类Beetl不允许调用,具体请参考高级用法。默认情况,java.lang.Runtime,和 java.lang.Process不允许在模板里调用。你自己的安全管理器也可以配置为不能直接访问DAO类(避免了以前jsp可以访问任意代码带来的危害) - 重要:请按照java规范写类名和方法名,属性名。这样便于beetl识别到底调用的是哪个类,哪个方法。否则会抛出错误 - 可以省略包名,只用类名。beetl将搜索包路径找到合适的类(需要设置配置“IMPORT_PACKAGE=包名.;包名.”,包名后需要跟一个“.”, 或者调用Configuration.addPkg)方法具体请参考附件配置文件说明 - 内部类(包括枚举)访问同java一样,如User类有个内部枚举类Gender,访问是User$Gender - 表达式是java风格,但参数仍然是beetl表达式,比如 @user.sayHello(user.name).这里user.sayHello是java调用,user.name 仍然是beetl表达式 #### 严格MVC控制 如果在配置文件中设置了严格MVC,则以下语法将不在模板文件里允许,否则将报出STRICK_MVC 错误. 严格MVC迫使主要渲染逻辑都靠Java完成,较少逻辑在页面完成 - 定义变量,为变量赋值,如var a = 12是非法的 - 算术表达式 如${user.age+12}是非法的 - 除了只允许布尔以外,不允许逻辑表达式和方法调用 如if(user.gender==1)是非法的 - 方法调用,如${subString(string,1)}是非法的 - Class方法和属性调用,如${@user.getName()}是非法的 - 严格的MVC,非常有助于逻辑与视图的分离,特别当逻辑与视图是由俩个团队来完成的。如果你嗜好严格MVC,可以调用groupTemplate.enableStrict() > 通过扩展引擎,可以按照自己的方法控制到底哪些语法是不允许在模板引擎中出现的,但这已经超出了Beetl模板的基础使用,实际上,通过扩展引擎,你可以裁剪Beetl语法。 #### 指令 指令格式为: DIRECTIVE 指令名 指令参数(可选) Beetl目前支持安全输出指令,分别是 - DIRECTIVE SAFE_OUTPUT_OPEN ; 打开安全输出功能,此指令后的所有表达式都具有安全输出功能, - DIRECTIVE SAFE_OUTPUT_CLOSE ; 关闭安全输出功能。详情参考安全输出 3.0 增加了允许所有模板安全输出,需要配置文件增加`SAFE_OUTPUT=true`,注意,安全输出会使得模板引擎隐藏了变量不存在的错误,除非你确定这是一个正常需求,否则,不要全局配置安全输出,建议使用`!`安全输出符号更好 #### 错误处理 Beetl能较为详细的显示错误原因,包括错误行数,错误符号,错误内容附近的模板内容,以及错误原因,如果有异常,还包括异常和异常信息。 默认情况下,仅仅在控制台显示,如下代码: ```javascript <% var a = 1; var b = a/0; %> ``` 运行此模板后,错误提示如下 ```javascript >>DIV_ZERO_ERROR:0 位于3行 资源:/org/beetl/sample/s0125/error1.txt 1|<% 2|var a = 1; 3|var b = a/0; 4| %> ``` ```javascript <% var a = 1; var b = a var c = a+2; %> ``` 运行此模板后 ```javascript >>缺少符号(PARSER_MISS_ERROR):缺少输入 ';' 在 'var' 位于4行 资源:/org/beetl/sample/s0125/error2.txt 1|<% 2|var a = 1; 3|var b = a 4|var c = a+2; 5| %> ``` 1. 默认的错误处理器仅仅像后台打印错误,并没有抛出异常,如果需要在render错误时候抛出异常到控制层,则可以使用org.beetl.core.ReThrowConsoleErrorHandler。不仅打印异常,还抛出BeetlException 2. 可以自定义异常处理器,比如把错误输出到 作为渲染结果一部分输出,或者输出更美观的html内容等,具体参考高级用法 3. 可以在配置文件不设置异常,这样Beetl引擎将不处理异常,用户可以在外部来处理(可以在外部调用ErrorHandler子类来显示异常) 4. 如果想校验模板是否正确,可以调用groupTemplate.validate....系列方法 #### Beetl小工具 BeetlKit 提供了一些便利的方法让你立刻能使用Beetl模板引擎。提供了如下方法 - `public static String render(String template, Map<String, Object> paras)` 渲染模板,使用paras参数,渲染结果作为字符串返回 - `public static void renderTo(String template, Writer writer, Map<String, Object> paras)` 渲染模板,使用paras参数 - `public static void execute(String script, Map<String, Object> paras)` 执行某个脚本 - `public static Map execute(String script, Map<String, Object> paras, String[] locals)` 执行某个脚本,将locals指定的变量名和模板执行后相应值放入到返回的Map里 - `public static Map executeAndReturnRootScopeVars(String script)` 执行某个脚本,返回所有顶级scope的所有变量和值 - `public static String testTemplate(String template, String initValue)` 渲染模板template,其变量来源于intValue脚本运行的结果,其所有顶级Scope的变量都将作为template的变量 ```java String template = "var a=1,c=2+1;"; Map result = executeAndReturnRootScopeVars(template); System.out.println(result); //输出结果是{c=3, a=1} ``` > BeetlKit 不要用于线上系统。仅仅作为体验Beetl功能而提供的,如果需要在线上使用这些功能,请参考该类源码自行扩展 #### Escape 可以使用\ 做escape 符号,如\$monkey\$ 将作为一个普通的文本,输出为$monkey$.再如为了在后加上美元符号(占位符恰好又是美元符号)可以用这俩种方式hello,it’s $money$\$, 或者Hello,it’s $money+"\$"$ 。如果要输出\符号本生,则需要用俩个\\,这点与javascript,java 语义一致.