Beetl2.2使用说明书20151201

1. 什么是Beetl

Beetl目前版本是2.2.8,相对于其余java模板引擎,具备功能齐全,语法直观,性能超高,以及编写的模板容易维护等特色。使得开发和维护模板有很好的体验。是新一代的模板引擎。总得来讲,它的特性以下:javascript

  • 功能完备:做为主流模板引擎,Beetl具备至关多的功能和其余模板引擎不具有的功能。适用于*各类应用场景*,从对响应速度有很高要求的大网站到功能繁多的CMS管理系统都适合。Beetl自己还具备不少独特功能来完成模板编写和维护,这是其余模板引擎所不具备的。css

  • 很是简单:相似Javascript语法和习俗,只要半小时就能经过半学半猜彻底掌握用法。拒绝其余模板引擎那种非人性化的语法和习俗。同时也能支持html 标签,使得开发CMS系统比较容易html

  • 超高的性能:Beetl 远超过主流java模板引擎性能(引擎性能5-6倍与freemaker,2倍于JSP。参考附录),并且消耗较低的CPU前端

  • 易于整合:Beetl能很容易的与各类web框架整合,如Spring MVC,JFinal,Struts,Nutz,Jodd,Servlet等。java

  • 支持模板单独开发和测试,即在MVC架构中,即便没有M和C部分,也能开发和测试模板。git

  • 扩展和个性化:Beetl支持自定义方法,格式化函数,虚拟属性,标签,和HTML标签. 同时Beetl也支持自定义占位符和控制语句起始符号也支持使用者彻底能够打造适合本身的工具包.程序员

关于性能

经过与主流模板引擎Freemarker,Vecloity以及JSP对比,Beetl6倍于Freemarker,2倍于JSP。这是由于宏观上,经过了优化的渲染引擎,IO的二进制输出,字节码属性访问加强,微观上,经过一维数组保存上下文Context,静态文本合并处理,经过重复使用字节数组来防止java频繁的建立和销毁数组,还使用模板缓存,运行时优化等方法。详情参考附录github

独特功能

Beetl有些功能是发展了10多年的模板引擎所不具有的,这些功能很是利于模板的开发和维护,以下web

  1. 自定义占位符和控制语句起始符号,这有利于减少模板语法对模板的倾入性,好比在html模板中,若是定义控制语句符号是<!--:和 -->,那么,大部分模板文件都能经过浏览器打开。有的使用者仅仅采用了单个符号@ (或者单个符号“~”)以及回车换号做为控制语句起始符号,这又能提升开发效率ajax

  2. 可单独测试的模板。无需真正的控制层和模型层,Beetl的模板就能够单独开发和测试

  3. 同时支持较为松散的MVC和严格的MVC,若是在模板语言里嵌入计算表达式,复杂条件表达式,以及函数调用有干涉业务逻辑嫌疑,你能够禁止使用这些语法。

  4. 强大的安全输出,经过安全输出符号!,能在模板变量,变量属性引用,for循环,占位符输出,try-catch中等各个地方提供安全输出,保证渲染正常

  5. 模板变量:运行将模板的某一部分输出像js那样赋值给一个变量,稍后再处理。利用模板变量能完成很是复杂的页面布局(简单的布局可以使用include,layout标签函数)

  6. 类型推测,能在运行的时候推测模板变量类型,从而优化性能,也能够经过注解的方法显示的说明模板变量属性(这是非必须的,但有助于IDE自动提示功能)

  7. 可插拔的设计,错误信息提示,模板引擎缓存机制,模板资源管理,本地调用的安全管理器,严格MVC限制,模板引擎自己都有默认的实现,但又彻底能够自定义以适合特定需求

  8. 加强的语法,如for-elsefor, select-case,安全输出符号!,省略的三元表达式 等,这些语法特别适合模板开发

  9. 局部渲染技术,结合如今js的ajax技术。

  10. 性能超高,具备最快的模板解释引擎,同时,又有较低的CPU消耗。5-6倍于国内使用的Freemaker。适合各种模板应用,如代码生成工具,CMS系统,普通网站,超高访问量的门户系统,和富客户端JS框架整合的后台管理应用

小白如何开始
  • 须要通读基本用法,大部分都是讲解语法,而语法跟js很接近,因此能够快速预览,但Beetl是针对模板设计, 因此像安全输出,标签和html标签,全局变量,临时变量和共享变量,布局技术,以及直接调用java代码等还须要认真读一遍。

  • 若是从事web开发,还须要阅读web集成里的第一节“web提供的全局变量”,若是web里还使用ajax技术,能够阅读“整合ajax的局部渲染技术”。

  • 包含有spring,jfinal,jodd,struts 等demo能够做为参考学习用 http://ibeetl.com/community/?/article/4

  • 任何问题,均可以在ibeetl.com 群上提问。目前答复率是100%,提问须要详细说明本身的指望,出错信息,附上代码或者图片

联系做者

做者:闲.大赋 (李家智)

QQ群:219324263

邮件:xiandafu@126.com

Beetl论坛:ibeetl.com

源码主页:https://github.com/javamonkey/beetl2.0

在线体验和代码分享 http://ibeetl.com:8080/beetlonline/

2. 基本用法

2.1. 从GroupTemplate开始

1
2
3
4
5
6
7
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内置了4种,分别是

  • StringTemplateResourceLoader:字符串模板加载器,用于加载字符串模板,如本例所示

  • FileResourceLoader:文件模板加载器,须要一个根目录做为参数构造,,传入getTemplate方法的String是模板文件相对于Root目录的相对路径

  • ClasspathResourceLoader:文件模板加载器,模板文件位于Classpath里

  • WebAppResourceLoader:用于webapp集成,假定模板根目录就是WebRoot目录,参考web集成章

  • MapResourceLoader : 能够动态存入模板

代码第5行将变量name传入模板里,其值是“Beetl”。 代码第6行是渲染模板,获得输出,template提供了多种得到渲染输出的方法,以下

  • tempalte.render() 返回渲染结果,如本例所示

  • template.renderTo(Writer) 渲染结果输出到Writer里

  • template.renderTo(OutputStream ) 渲染结果输出到OutputStream里

  1. 关于如何使用模板资源加载器,请参考下一节

  2. 如何对模板进行配置,请参考下一节

2.2. 模板基础配置

Beetl提供不但功能齐全,并且还有不少独特功能,经过简单的配置文件,就能够定义众多的功能,默认状况下,Configuration类老是会先加载默认的配置文件(位于/org/beetl/core/beetl-default.properties,做为新手,一般只须要关注3,4,5,6行定界符的配置,以及11行模板字符集的配置就能够了,其余配置会在后面章节陆续提到)下,其内容片段以下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#默认配置
ENGINE=org.beetl.core.engine.FastRuntimeEngine
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= /
#是否检测文件变化
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

第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行指定了默认使用的模板资源加载器

第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的配置属性,若是有重复,用后者代替前者的配置

1
2
3
#自定义配置
DELIMITER_STATEMENT_START=<!--:
DELIMITER_STATEMENT_END=-->

2.3. 模板资源加载器

资源加载器是根据String值获取Resource实例的工场类,同时资源加载器还要负责响应模板引擎询问模板是否变化的调用。对于新手来讲,无需考虑模板资源加载器如何实现,只须要根据本身场景选择系统提供的三类模板资源加载器便可

2.3.1. 字符串模板加载器

在建立GroupTemplate过程当中,若是传入的是StringTemplateResourceLoader,则容许经过调用gt.getTemplate(String template)来获取模板实例对象,如2.1所示

2.3.2. 文件资源模板加载器

更一般状况下,模板资源是以文件形式管理的,集中放在某一个文件目录下(如webapp的模板根目录就多是WEB-INF/template里),所以,可使用FileResourceLoader来加载模板实例,以下代码:

1
2
3
4
5
6
7
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来加载模板

2.3.3. Classpath资源模板加载器

还有种常状况下,模板资源是打包到jar文件或者同Class放在一块儿,所以,可使用ClasspathResourceLoader来加载模板实例,以下代码:

1
2
3
4
5
6
ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader(); Configuration cfg = Configuration.defaultConfiguration(); GroupTemplate gt = new GroupTemplate(resourceLoader, cfg); Template t = gt.getTemplate("/org/beetl/sample/s01/hello.txt"); String str = t.render(); System.out.println(str); 

第1行代码指定了模板根目录,即搜索模板的时候从根目录开始,若是new ClasspathResourceLoader("/template"),则表示搜索/template下的模板。此处用空构造函数,表示搜索路径是根路径,且字符集默认字符集UTF-8.

第4行经过模板的相对路径org/beetl/sample/s01/hello.txt来加载模板

2.3.4. WebApp资源模板加载器

WebAppResourceLoader 是用于web应用的资源模板加载器,默认根路径是WebRoot目录。也能够经过制定root属性来设置相对于WebRoot的的模板根路径,从安全角考虑,建议放到WEB-INF目录下

以下是Jfinal集成 里初始化GroupTemplate的方法

1
2
3
Configuration cfg = Configuration.defaultConfiguration(); WebAppResourceLoader resourceLoader = new WebAppResourceLoader(); groupTemplate = new GroupTemplate(resourceLoader, cfg); 

WebAppResourceLoader 假定 beetl.jar 是位于 WEB-INF/lib 目录下,所以,能够经过WebAppResourceLoader类的路径来推断出WebRoot路径从而指定模板根路径。全部线上环境通常都是如此,若是是开发环境或者其余环境不符合此假设,你须要调用resourceLoader.setRoot() 来指定模板更路径

2.3.5. 自定义资源模板加载器

有时候模板可能来自文件系统不一样目录,或者模板一部分来自某个文件系统,另一部分来自数据库,还有的状况模板多是加密混淆的模板,此时须要自定义资源加载,继承ResouceLoader才能实现模板功能,这部分请参考高级部分

2.4. 定界符与占位符号

Beetl模板语言相似JS语言和习俗,只须要将Beetl语言放入定界符号里便可,如默认的是<% %> ,占位符用于静态文本里嵌入占位符用于输出,以下是正确例子

1
2
3
4
5
6
<%
var a = 2; var b = 3; var result = a+b; %> hello 2+3=${result} 

千万不要在定界符里使用占位符号,由于占位符仅仅嵌在静态文本里,以下例子是错误例子

1
2
3
4
<%
var a = "hi"; var c = ${a}+"beetl"; //应该是var c = a+"beetl" %> 

每次有人问我如上例子为啥不能运行的时候,我老是有点憎恶velocity 带来的这种非人性语法

定界符号里是表达式,若是表达式跟定界符有冲突,能够在表达式里用 “\” 符号,如

1
2
${[1,2,3]} //输出一个json列表 ${ {key:1,value:2 \} } //输出一个json map,} 须要加上\ 

定界符和占位符 一般还有别的选择,以下定界符

  • @ 和回车换行 (此时,模板配置DELIMITER_STATEMENT_END= 或者 DELIMITER_STATEMENT_END=null 均可以)

  • #: 和回车换行

  • <!--: 和 -→

  • <!--# 和 -→

  • <? 和 ?>

占位符: - - #{ } - # #

你也能够与团队达成一致意见来选择团队喜好择定界符号和占位符号。

2.5. 注释

Beetl语法相似js语法,因此注释上也同js同样: 单行注释采用//

多行注视采用/**/

1
2
3
4
5
6
7
8
<%
/*此处是一个定义变量*/ var a = 3; //定义一个变量. /* 如下内容都将被注释 %> <% */ %> 

第2行是一个多行注释

第3行是一个单行注释

第5行到第8行采用的是多行注释,所以里面有内容也是注释,模板将不予处理

2.6. 临时变量定义

在模板中定义的变量成为临时变量,这相似js中采用var 定义的变量,以下例子

1
2
3
4
5
6
7
8
<%

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; %> 

2.7. 全局变量定义

全局变量是经过template.binding传入的变量,这些变量能在模板的任何一个地方,包括子模板都能访问到。如java代码里

1
2
3
4
5
6
7
8
template.binding("list",service.getUserList()); //在模板里 <% for(user in list){ %> hello,${user.name}; <%}%> 

2.8. 共享变量

共享变量指在全部模板中均可以引用的变量,可过groupTemplate.setSharedVars(Map<String, Object> sharedVars)传入的变量,这些变量能在 全部模板 的任何一个地方

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
.....
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); 
1
2
3
4
//t1.txt
hi,${name} //t2.txt hello,${name} 

2.9. 模板变量

模板变量是一种特殊的变量,便可以将模板中任何一段的输出赋值到该变量,并容许稍后在其余地方使用,以下代码

1
2
3
4
5
6
7
8
<%
var content = { var c = "1234"; print(c); %> 模板其余内容<%}; %> 

第2行定义了一个模板变量content = { …} ; 此变量跟临时变量同样,能够在其余地方使用,最多见的用户是用于复杂的布局。请参考高级用法布局

2.10. 引用属性

属性引用是模板中的重要一部分,beetl支持属性引用若是javascript的支持方式同样,以下

1 Beetl支持经过”.”号来访问对象的的属性,若是javascript同样。若是User对象有个getName()方法,那么在模板中,能够经过${xxx.name}来访问

2 若是模板变量是数组或者List类,这能够经过[] 来访问,如${userList[0]}

3 若是模板变量是Map类,这能够经过[]来访问,如${map[“name”]},若是key值是字符串类型,也可使用${map.name}.但不建议这么使用,由于会让模板阅读者误觉得是一个Pojo对象

4 Beetl也支持Generic Get方式,即若是对象有一个public Object get(String key)方法,能够经过”.”号或者[]来访问,譬如 ${activityRecord.name}或者${activityRecord[“name”] }都将调用activityRecord的 get(String key)方法。若是对象既有具体属性,又有Generic get(这种模型设计方式是不值得鼓励),则以具体属性优先级高.

5 Beetl也能够经过[]来引用属性,如${user[“name”]} 至关于${user.name}.这跟javascript保持一致。但建议不这么作,由于容易让阅读模板的人误认为这是一个Map类型

6 Beetl 还能够定位额外的对象属性,而无需更改java对象,这叫着虚拟属性,如,对于全部集合,数组,都有共同的虚拟熟悉size.虚拟属性是“.~”+虚拟1属性名

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
template.binding("list",service.getUserList()); template.binding("pageMap",service.getPage()); //在模板里 总共 ${list.~size} <% for(user in list){ %> hello,${user.name}; <%}%> 当前页${pageMap['page']},总共${pageMap["total"]} 

2.11. 算数表达式

Beetl支持相似javascript的算术表达式和条件表达式,如+ - * / % 以及(),以及自增++,自减--

1
2
3
4
5
6
7
8
<%
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,其后的计算和输出以及逻辑表达式都将按照长精度类型来考虑。

2.12. 逻辑表达式

Beetl支持相似Javascript,java的条件表达式 如>, <, == ,!=,>= , ⇐ 以及 !, 还有&&和 || ,还有三元表达式等,以下例子

1
2
3
4
5
6
7
8
9
<%
var a = 1; var b=="good"; var c = null; if(a!=1&&b=="good"&&c==null){ ...... } %> 

三元表达式若是只考虑true条件对应的值的话,能够作简化,以下俩行效果是同样的。

1
2
3
4
5
<%
 var a = 1 ; %> ${a==1?"ok":''} ${a==1?"ok"} 

2.13. 循环语句

Beetl支持丰富的循环方式,如for-in,for(exp;exp;exp),以及while循环,以及循环控制语句break;continue; 另外,若是没有进入for循环体,还能够执行elsefor指定的语句。

2.13.1. for-in

for-in循环支持遍历集合对象,对于List和数组来讲以及Iterator,对象就是集合对象,对于Map来讲,对象就是Map.entry,以下俩个例子

1
2
3
4
5
6
7
<%
for(user in userList){ print(userLP.index); print(user.name); } %> 

第三行代码userLP是Beetl隐含定义的变量,能在循环体内使用。其命名规范是item名称后加上LP,他提供了当前循环的信息,如

  • userLP.index :当前的索引,从1开始

  • userLP.size:集合的长度

  • userLP.first 是不是第一个

  • userLP.last 是不是最后一个

  • userLP.even 索引是不是偶数

  • userLP.odd 索引是不是奇数

如何记住后缀是LP,有俩个诀窍,英语棒的是Loop的缩写,拼音好的是老婆的拼音缩写,这可让程序员每次写到这的时候都会想一想老婆(无论有没有,哈哈)

以下是Map使用例子

1
2
3
4
5
6
7
8
<%
for(entry in map){ var key = entry.key; var value = entry.value; print(value.name); } %> 

2.13.2. for(exp;exp;exp)

对于渲染逻辑更为常见的是经典的for循环语句,以下例子

1
2
3
4
5
6
<%
var a = [1,2,3]; for(var i=0;i<a.~size;i++){ print(a[i]); } %> 

2.13.3. while

对于渲染逻辑一样常见的有的while循环语句,以下例子

1
2
3
4
5
6
7
<%
var i = 0; while(i<5){ print(i); i++; } %> 

2.13.4. elsefor

不一样于一般程序语言,若是没有进入循环体,则不需额外的处理,模板渲染逻辑更常见状况是若是没有进入循环体,还须要作点什么,所以,对于for循环来讲,还有elsefor 用来表达若是循环体没有进入,则执行elsefor 后的语句

1
2
3
4
5
6
7
8
<%
var list = []; for(item in list){ }elsefor{ print("未有记录"); } %> 

2.14. 条件语句

2.14.1. if else

同js同样,支持if else,以下例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<%
var a =true; var b = 1; if(a&&b==1){ }else if(a){ }else{ } %> 

2.14.2. switch-case

同js同样,支持switch-case,以下例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<%
var b = 1; switch(b){ case 0: print("it's 0"); break; case 1: print("it's 1"); break; default: print("error"); } %> 

switch变量能够支持任何类型,而不像js那样只能是整形

2.14.3. select-case

select-case 是switch case的加强版。他容许case 里有逻辑表达式,同时,也不须要每一个case都break一下,默认遇到合乎条件的case执行后就退出。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<%
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.其格式是

1
2
3
4
5
6
select { case boolExp,orBoolExp2: doSomething(); } %> 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<%
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"); } %> 

2.15. try-catch

一般模板渲染逻辑不多用到try-catch 但考虑到渲染逻辑复杂性,以及模板也有不可控的地方,因此提供try catch,在渲染失败的时候仍然能保证输出正常

1
2
3
4
5
6
7
8
<%
try{ callOtherSystemView() }catch(error){ print("暂时无数据"); } %> 

error表明了一个异常,你能够经过error.message 来获取可能的错误信息

也能够省略catch部分,这样出现异常,不作任何操做

2.16. 虚拟属性

虚拟属性也是对象的属性,可是虚拟的,非模型对象的真实属性,这样的好处是当模板须要额外的用于显示的属性的时候但又不想更改模型,即可以采用这种办法 如beetl内置的虚拟属性.~size 针对了数组以及集合类型。

1
2
${user.gender} ${user.~genderShowName} 

~genderShowName 是虚拟属性,其内部实现根据boolean变量gender来显示性别

如何完成虚拟属性,请参考高级用法

2.17. 函数调用

Beetl内置了少许实用函数,能够在Beetl任何地方调用。以下例子是调用date 函数,不传参数状况下,返回当前日期

1
2
3
4
5
6
<%
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内置函数请参考附录,如下列出了经常使用的函数

  • date 返回一个java.util.Date类型的变量,如 date() 返回一个当前时间(对应java的java.util.Date); ${date( "2011-1-1" , "yyyy-MM-dd" )} 返回指定日期

  • print 打印一个对象 print(user.name);

  • println 打印一个对象以及回车换行符号,回车换号符号使用的是模板自己的,而不是本地系统的.若是仅仅打印一个换行符,则直接调用println() 便可

  • nvl 函数nvl,若是对象为null,则返回第二个参数,不然,返回本身 nvl(user,"不存在")

  • isEmpty 判断变量或者表达式是否为空,变量不存在,变量为null,变量是空字符串,变量是空集合,变量是空数组,此函数都将返回true

  • isNotEmpty 同上,判断对象是否不为空

  • has 变量名为参数,判断是否存在此全局变量,如 has(userList),相似于1.x版本的exist("userList"),但不须要输入引号了

  • assert 若是表达式为false,则抛出异常

  • trunc 截取数字,保留指定的小数位,如trunc(12.456,2) 输出是12.45

  • 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

  • pageCtx ,仅仅在web开发中,设置一个变量,而后能够在页面渲染过程当中,调用此api获取,如pageCtx("title","用户添加页面"),在其后任何地方,能够pageCtx("title") 获取该变量

2.18. 安全输出

安全输出是任何一个模板引擎必须重视的问题,不然,将极大困扰模板开发者。Beetl中,若是要输出的模板变量为null,则beetl将不作输出,这点不一样于JSP,JSP输出null,也不一样于Feemarker,若是没有用!,它会报错.

模板中还有俩种状况会致使模板输出异常

  • 有时候模板变量并不存在(譬如子模板里)

  • 模板变量为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将会忽略此异常,继续渲染

值得注意的是,在变量后加上!不只仅能够应用于占位符输出(但主要是应用于占位符输出),也能够用于表达式中,如:

1
2
3
4
5
6
7
8
<%

<% var k = user.name!'N/A'+user.age!; %> ${k} %> 

若是user为null,则k值将为N/A

在有些模板里,可能整个模板都须要安全输出,也可能模板的部分须要安全输出,使用者没必要为每个表达式使用!,可使用beetl的安全指示符号来完成安全输出 如:

1
2
3
4
5
6
7
8
9
<%
DIRECTIVE SAFE_OUTPUT_OPEN; %> ${user.wife.name} 模板其余内容均能安全输出…… <% //关闭安全输出。 DIRECTIVE SAFE_OUTPUT_CLOSE; %> 

Beetl不建议每个页面都使用DIRECTIVE SAFE_OUTPUT_OPEN,这样,若是若是真有不指望的错误,不容易及时发现,其次,安全输出意味着beetl会有额外的代码检测值是否存在或者是否为null,性能会略差点。因此建议及时关闭安全输出(这不是必须的,但页面全部地方是安全输出,可能不容易发现错误)

在for-in 循环中 ,也能够为集合变量增长安全输出指示符号,这样,若是集合变量为null,也能够不进入循环体,如:

1
2
3
4
5
6
7
8
<%
var list = null; for(item in list!){ }eslefor{ print("no data"); } %> 

2.18.1. 变量是否存在

判断变量是否存在,能够采用内置的has或者isEmpty方法来判断,参数是变量,如

1
2
3
4
5
<%
if(has(flag)){ print("not exit") } %> 

若是须要判断变量是否存在,若是存在,还有其余判断条件,一般都这么写

1
2
3
4
5
<%
if(has(flag)||flag==0){ //code } %> 

若是flag不存在,或者flag存在,但值是0,都将执行if语句

可是,有更为简便的方法是直接用安全输出,如

1
2
3
4
5
<%
if(flag!0==0){ //code } %> 

flag!0 取值是这样的,若是flag不存在,则为0,若是存在,则取值flag的值,相似三元表达式 has(flag)?falg:0

2.18.2. 安全输出表达式

安全输出表达式能够包括

  • 字符串常量,如 ${user.count!"无结果"}

  • boolean常量 ${user.count!false}

  • 数字常量,仅限于正数,由于若是是负数,则相似减号,容易误用,所以,若是须要表示负数,请用括号,如${user.count!(-1)}

  • class直接调用,如${user.count!@User.DEFAULT_NUM}

  • 方法调用,如 ${user.count!getDefault() }

  • 属性引用,如 ${user.count!user.maxCount }

  • 任何表达式,须要用括号

2.19. 格式化

几乎全部的模板语言都支持格式化,Beetl也不列外,以下例子Beetl提供的内置日期格式

1
2
3
4
<% 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 设置了了格式化函数,所以上面的例子能够简化为

1
${date,yyyy-MM-dd}. 

Beetl针对日期和数字类型提供的默认的格式化函数,在org/beetl/core/beetl-default.properties里,注册了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
##内置的格式化函数 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 

2.20. 标签函数

所谓标签函数,即容许处理模板文件里的一块内容,功能等于同jsp tag。如Beetl内置的layout标签

index.html

1
2
3
4
5
<%
layout("/inc/layout.html",{title:'主题'}){ %> Hello,this is main part <%} %> 

layout.html

1
2
3
title is ${title} body content ${layoutContent} footer 

第1行变量title来自于layout标签函数的参数

第2行layoutContent 是layout标签体{}渲染后的结果

关于layout标签,参考高级主题布局

Beetl内置了另一个标签是include,容许 include 另一个模板文件

1
2
3
<%
include("/inc/header.html"){} %> 

在标签中,{} 内容将依据标签的实现而执行,layout标签将执行{}中的内容,而include标签则忽略标签体内容。

关于如何实现标签函数,请参考高级主题,以下是一个简单的的标签函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class CompressTag extends Tag { @Override public void render() { BodyContent content = getBodyContent(); String content = content.getBody(); String zip = compress(conent); ctx.byteWriter.write(zip); } } 

2.21. HTML标签

Beetl 也支持HTML tag形式的标签, 区分beetl的html tag 与 标准html tag。如设定HTML_TAG_FLAG=#,则以下html tag将被beetl解析

1
2
3
<#footer style=simple/> <#richeditor id=ridpath="${ctxPath}/upload" name=rnamemaxlength=${maxlength}> ${html} 其余模板内容 </#richdeitor> <#html:input id=aaaa/> 

如对于标签footer,Beetl默认会寻找WebRoot/htmltag/footer.tag(能够经过配置文件修改路径和后缀) ,内容以下:

1
2
3
4
5
<%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文件里自行格式化

  • html tag 属性名将做为 其对应模板的变量名。

  • 默认机制下,全局变量都将传给html tag对应的模板文件,这个跟include同样。固然,这机制也能够改变,对于标签来讲,一般是做为一个组件存在,也不必定须要彻底传送全部全局变量,而只传送(request,session,这样变量),所以须要从新继承org.beetl.ext.tag.HTMLTagSupportWrapper.并重载callHtmlTag方法。并注册为htmltag标签。具体请参考https://github.com/javamonkey/beetl2.0/blob/master/beetl-core/src/test/java/org/beetl/core/tag/HtmlTagTest.java

若是采用模板来写html标签功能不够强大,beetl支持写标签函数(参考上一节)来实现html标签,标签函数args[0]表示标签名,这一般没有什么用处,args[1] 则是标签的属性,参数是个map,key是html tag的属性,value是其属性值,以下用java完成的html 标签用于输出属性值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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"); try { this.ctx.byteWriter.writeString(value); } catch (IOException e) { } } } 

若是注册gt.registerTag("simpleTag", SimpleHtmlTag.class); 则以下模板输出了attr属性值abc

1
<#simpleTag attr="abc"></#simpleTag> 

HTML_TAG_FLAG默认为#用来区别是不是beetl的html tag,你也能够设置成其余符号,好比 "my:",这样,<my:table></my:table> 实际上是一个指向table.tag的标签实现

2.22. 绑定变量的HTML标签

对于html标签(参考上一节),Beetl还 支持将标签实现类(java代码)里的对象做为临时变量,被标签体引用。此时须要实现GeneralVarTagBinding (此类是Tag的子类) 该类提供另外3个个方法 - void binds(Object… array) 子类在render方法里调用此类以实现变量绑定,绑定顺序同在模板中申明的顺序 - void bind(String name, Object value) 子类在render方法里调用此类以实现变量绑定,name是模板中申明的变量名,用此方法绑定不如binds更灵活,再也不推荐 - Object getAttributeValue 得到标签的属性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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。咱们再看看模板如何写的

1
2
3
 <#tag limit="3" ; value>
        ${value}
 </#tag> 

相似于常规html标签,须要在标签的最后的属性定义后面加上分号 ";" 此分号表示这个是一个须要在标签运行时须要绑定变量的标签。后跟上要绑定的变量列表,如上例只绑定了一个value变量,若是须要绑定多个变量,则用逗号分开,如var1,var2 上。若是后面没有变量列表,只有分号,则默认绑定到标签名同名的变量上. 若是标签有namesapce,则默认绑定订的变量名不包含namespace

注意,因为标签使用由于太长可能换行或者是文本格式化致使换行,目前beetl只容许在属性之间换行,不然,将报标签解析错误。

默认状况下,若是标签属性出现了var(能够经过配置文件改为其余属性名),也认为是绑定变量的标签,如上面的例子也能够这么写

1
2
3
 <#tag limit="3" var="value">
        ${value}
 </#tag> 

var属性的值能够是个以逗号分开的变量名列表,如var="total,customer,index"

2.23. 直接调用java方法和属性

1
2
3
4
5
6
7
${@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; %> 

能够调用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

2.24. 严格MVC控制

若是在配置文件中设置了严格MVC,则如下语法将不在模板文件里容许,不然将报出STRICK_MVC 错误

  • 定义变量,为变量赋值,如var a = 12是非法的

  • 算术表达式 如${user.age+12}是非法的

  • 除了只容许布尔之外,不容许逻辑表达式和方法调用 如if(user.gender==1)是非法的

  • 方法调用,如${subString(string,1)}是非法的

  • Class方法和属性调用,如${@user.getName()}是非法的

  • 严格的MVC,很是有助于逻辑与视图的分离,特别当逻辑与视图是由俩个团队来完成的。若是你嗜好严格MVC,能够调用groupTemplate.enableStrict()

经过重载AntlrProgramBuilder,能够按照本身的方法控制到底哪些语法是不容许在模板引擎中出现的,但这已经超出了Beetl模板的基础使用

2.25. 指令

指令格式为: DIRECTIVE 指令名 指令参数(可选) Beetl目前支持安全输出指令,分别是

  • DIRECTIVE SAFE_OUTPUT_OPEN ; 打开安全输出功能,此指令后的全部表达式都具备安全输出功能,

  • DIRECTIVE SAFE_OUTPUT_CLOSE ; 关闭安全输出功能。详情参考安全输出

  • DIRECTIVE DYNAMIC varName1,varName2 …指示后面的变量是动态类型,Beetl应该考虑为Object. 也能够省略后面的变量名,则表示模板里全部变量都是Object

1
2
<% DIRECTIVE DYNAMIC idList; for(value in idList) ..... 

DYNAMIC 一般用在组件模板里,由于组件模板能够接收任何类型的对象。如列表控件,能够接收任何含有id和 value属性的对象。

1 注意 DYNAMIC 后的变量名也容许用引号,这主要是兼容Beetl1.x版本

2 Beetl1.x 指令都是大写,当前版本也容许小写,如 directive dynamic idList

2.26. 类型声明

Beetl 本质上仍是强类型的模板引擎,即模板每一个变量类型是特定的,在模板运行过程当中,beetl 会根据全局变量自动推测出模板中各类变量和表达式类型。 也能够经过类型申明来讲明beetl全局变量的类型,以下格式

1
2
3
4
5
<%
/** *@type (List<User> idList,User user) */ for(value in idList) ..... 

类型申明必须放到多行注释里,格式是@type( … ),里面的申明相似java方法的参数申明。正如你看到的类型申明是在注释里,也就代表了这在Beetl模板引擎中不是必须的,或者你只须要申明一部分便可,之因此提供可选的类型说明,是由于

  • 提升一点性能

  • 最重要的是,提升了模板的可维护性。可让模板维护者知道变量类型,也可让将来的ide插件根据类型声明来提供属性提示,重构等高级功能

须要注意的是,若是在类型声明里提供的是类名,而不是类全路径,这样必须在配置文件里申明类的搜索路径((须要设置配置IMPORT_PACKAGE=包名.;包名.,或者调用Configuration.addPkg)),默认的搜索路径有java.util. 和 java.lang.

2.27. 错误处理

Beetl能较为详细的显示错误缘由,包括错误行数,错误符号,错误内容附近的模板内容,以及错误缘由,若是有异常,还包括异常和异常信息。 默认状况下,仅仅在控制台显示,以下代码:

1
2
3
4
<%
var a = 1; var b = a/0; %> 

运行此模板后,错误提示以下:

1
2
3
4
5
>>DIV_ZERO_ERROR:0 位于3行 资源:/org/beetl/sample/s0125/error1.txt 1|<% 2|var a = 1; 3|var b = a/0; 4|%> 
1
2
3
4
5
<%
var a = 1; var b = a var c = a+2; %> 

运行此模板后

1
2
3
4
5
6
>>缺乏符号(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子类来显示异常)

2.28. 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的变量

  • public static String testTemplate(String template, String initValue) 渲染模板template,其变量来源于intValue脚本运行的结果,其全部顶级Scope的变量都将做为template的变量

1
2
3
4
String template = "var a=1,c=2+1;"; Map result = executeAndReturnRootScopeVars(template); System.out.println(result); //输出结果是{c=3, a=1} 

BeetlKit 不要用于线上系统。仅仅做为体验Beetl功能而提供的,若是须要在线上使用这些功能,请参考该类源码自行扩展

2.29. 变量不存在

对于编程语言来讲,变量不存在则在编译期就会报错,对于那种解释语言,变量也是要么存在,要么不存在,不可能一段代码里有俩种状况,但对于模板语言来讲,一段模板里的变量可能有这俩种状态,如新增和修改共用一个模板文件就会出现这种状况。若是变量有可能不存在,则用安全输出符号"!",以下代码:

1
<span>${user.name!}</span> 

对于逻辑表达式,也可使用安全输出符号,如:

1
2
3
4
<%
if(null==user.name!) //安全输出符号后若是未有表达式,则返回null if(''==user.name!'') //若是user.name不存在或者为null,则返回空字符串。或者user.name自己是空字符串 %> 

系统提供了一些函数,也能够用于判断变量是否存在

1
2
3
4
5
6
7
8
<%
if(has(user)) //接受一个变量名,若是存在user变量,返回true if(isEmpty(user.name) /* 若是user不存在,或者为null,或者user.name是空为null, 或者user.name是空字符串,都返回true */ %> 

isEmtpy 能够接受表达式,能够判断集合,数组,字符串为空状况,详细参考该函数说明

2.30. 琐碎功能

  • 对齐:我发现别的模板语言要是作到对齐,很是困难,使用Beetl你彻底不用担忧,好比velocty,stringtemlate,freemarker例子都出现了不对齐的状况,影响了美观,Beetl彻底无需担忧输出对齐

  • Escape:可使用\ 作escape 符号,如\$monkey\$ 将做为一个普通的文本,输出为$monkey$.再如为了在后加上美圆符号(占位符刚好又是美圆符号)能够用这俩种方式hello,it’s $money$\$, 或者Hello,it’s $money+"\$"$ 。若是要输出\符号本生,则须要用俩个\\,这点与javascript,java 语义一致.

3. 高级用法

3.1. 配置GroupTemplate

Beetl建议经过配置文件配置配置GroupTemplate,主要考虑到将来可能IDE插件会支持Beetl模板,模板的属性,和函数等若是能经过配置文件获取,将有助于IDE插件识别。 配置GroupTemplate有俩种方法

  • 配置文件: 默认配置在/org/beetl/core/beetl-default.properties 里,Beetl首先加载此配置文件,而后再加载classpath里的beetl.properties,并用后者覆盖前者。配置文件经过Configuration类加载,所以加载完成后,也能够经过此类API来修改配置信息

  • 经过调用GroupTemplate提供的方法来注册函数,格式化函数,标签函数等

配置文件分为三部分,第一部分是基本配置,在第一节讲到过。第二部分是资源类配置,能够在指定资源加载类,以及资源加载器的属性,以下

1
2
3
4
5
6
RESOURCE_LOADER=org.beetl.core.resource.ClasspathResourceLoader #资源配置resource后的属性只限于特定ResourceLoader #classpath 根路径 RESOURCE.root= / #是否检测文件变化 RESOURCE.autouCheck= true 

第一行指定了类加载器,第二行指定了模板根目录的路径,此处/ 表示位于classpath 根路径下,第三行是否自动检测模板变化,默认为true,开发环境下自动检测模板是否更改。关于如何如何自定义ResouceLoader,请参考下一章

配置文件第三部分是扩展部分,如方法,格式化函数等

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#####  扩展 ############## ## 内置的方法 FN.date = org.beetl.ext.fn.DateFunction FN.nvl = org.beetl.ext.fn.NVLFunction ................. ##内置的功能包 FNP.strutil = org.beetl.ext.fn.StringUtil ##内置的格式化函数 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 ## 标签类 TAG.include= org.beetl.ext.tag.IncludeTag TAG.includeFileTemplate= org.beetl.ext.tag.IncludeTag TAG.layout= org.beetl.ext.tag.LayoutTag TAG.htmltag= org.beetl.ext.tag.HTMLTagSupportWrapper 

fn前缀表示Function,fnp前缀表示FunctionPackage,FT表示format函数,FTC表示类的默认Format函数,TAG表示标签类。Beetl强烈建议经过配置文件加载扩展。以便随后IDE插件能识别这些注册函数

3.2. 自定义方法

3.2.1. 实现Function

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Print implements Function { public String call(Object[] paras, Context ctx) { Object o = paras[0]; if (o != null) { try { ctx.byteWriter.write(o.toString()); } catch (IOException e) { throw new RuntimeException(e); } } return ""; } 

call方法有俩个参数,第一个是数组,这是由模板传入的,对应着模板的参数,第二个是Context,包含了模板的上下文,主要提供了以下属性

  • byteWriter 输出流

  • template 模板自己

  • gt GroupTemplate

  • globalVar 该模板对应的全局变量

  • byteOutputMode 模板的输出模式,是字节仍是字符

  • safeOutput 模板当前是否处于安全输出模式

  • 其余属性建议不熟悉的开发人员不要乱动

1 call方法要求返回一个Object,若是无返回,返回null便可

2 为了便于类型判断,call方法最好返回一个具体的类,如date函数返回的就是java.util.Date

3 call方法里的任何异常应该抛出成Runtime异常

3.2.2. 使用普通的java类

尽管实现Function对于模板引擎来讲,是效率最高的方式,但考虑到不少系统只有util类,这些类里的方法仍然能够注册为模板函数。其规则很简单,就是该类的全部public方法。若是需还要Context 变量,则须要在方法最后一个参数加上Context便可,如

1
2
3
4
5
6
7
8
public class util { public String print(Object a, Context ctx) { ............... } 

注意

1 从beetl效率角度来说,采用普通类效率不如实现Function调用

2 采用的普通java类尽可能少同名方法。这样效率更低。beetl调用到第一个适合的同名方法。而不像java那样找到最匹配的

3 方法名支持可变数组做为参数

4 方法名最后一个参数若是是Context,则beetl会传入这个参数。

3.2.3. 使用模板文件做为方法

能够不用写java代码,模板文件也能做为一个方法。默认状况下,须要将模板文件放到Root的functions目录下,且扩展名为.html(能够配置文件属性来修改此俩默认值) 方法参数分别是para1,para2…..

以下root/functions/page.fn

1
2
3
4
5
<%
//para0,para1 由函数调用传入 var current = para0,total = para1,style=para2!'simple' %> 当前页面 ${current},总共${total} 

则在模板中

1
2
3
<%
page(current,total); %> 

容许使用return 表达式返回一个变量给调用者,如模板文件functions\now.html

1
2
3
<%
        return date(); %> 

在任何模板里均可以调用:

1
hello time is ${now(),yyyy-MM-dd} 

也能够在functions创建子目录,这样function则具备namespace,其值就是文件夹名

3.3. 自定义格式化函数

须要实现Format接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class DateFormat implements Format { public Object format(Object data, String pattern) { if (data == null) return null; if (Date.class.isAssignableFrom(data.getClass())) { SimpleDateFormat sdf = null; if (pattern == null) { sdf = new SimpleDateFormat(); } else { sdf = new SimpleDateFormat(pattern); } return sdf.format((Date) data); } else { throw new RuntimeException("Arg Error:Type should be Date"); } } 

data 参数表示须要格式化的对象,pattern表示格式化模式,开发时候须要考虑pattern为null的状况

也能够实现ContextFormat 类抽象方法,从而获得Context,获取外的格式化信息。

1
        public abstract Object format(Object data,String pattern,Context ctx); 

3.4. 自定义标签

标签形式有俩种,一种是标签函数,第二种是html tag。第二种实际上在语法解析的时候会转化成第一种,其实现是HTMLTagSupportWrapper,此类将会寻找root/htmltag目录下同名的标签文件做为模板来执行。相似普通模板同样,在此就不详细说了

3.4.1. 标签函数

标签函数相似jsp2.0的实现方式,须要实现Tag类的render方法便可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class DeleteTag extends Tag { @Override public void render() { // do nothing,just ignore body ctx.byteWriter.write("被删除了,付费能够看") } } 

如上一个最简单的Tag,将忽略tag体,并输出内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class XianDeDantengTag extends Tag { @Override public void render() { doBodyRender(); } } 

此类将调用父类方法doBodyRender,渲染tag body体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class CompressTag extends Tag { @Override public void render() { BodyContent content = getBodyContent(); String content = content.getBody(); String zip = compress(conent); ctx.byteWriter.write(zip); } } 

此类将调用父类方法getBodyContent ,得到tag body后压缩输出

tag类提供了以下属性和方法供使用

  • args 传入标签的参数

  • gt GroupTemplate

  • ctx Context

  • bw 当前的输出流

  • bs 标签体对应的语法树,不熟悉勿动

3.5. 自定义虚拟属性

能够为特定类注册一个虚拟属性,也能够为一些类注册虚拟属性

  • public void registerVirtualAttributeClass(Class cls, VirtualClassAttribute virtual) 实现VirtualClassAttribute方法能够为特定类注册一个须要属性,以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
gt.registerVirtualAttributeClass(User.class, new VirtualClassAttribute() { @Override public String eval(Object o, String attributeName, Context ctx) { User user = (User) o; if(attributeName.equals("ageDescritpion")){ if (user.getAge() < 10) { return "young"; } else { return "old"; } } } }); 

User类的全部虚拟属性将执行eval方法,此方法根据年纪属性来输出对应的描述。

  • public void registerVirtualAttributeEval(VirtualAttributeEval e) 为一些类注册须要属性,VirtualAttributeEval.isSupport方法将判断是否应用虚拟属性到此类

以下是虚拟属性类的定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public interface VirtualClassAttribute { public Object eval(Object o, String attributeName, Context ctx); } public interface VirtualAttributeEval extends VirtualClassAttribute { public boolean isSupport(Class c, String attributeName); } 

3.6. 使用额外的资源加载器

某些状况下,模板来源不止一处,GroupTemplate配置了一个默认的资源加载器,若是经过gt.getTemplate(key),将调用默认的ResourceLoader,获取模板内容,而后转化为beetl脚本放入到缓存里。你也能够传入额外的资源管理器加载模板,经过调用gt.getTemplate(key,otherLoader)来完成;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
GroupTemplate gt = new GroupTemplate(conf,fileLoader) //自定义,参考下一节 MapResourceLoader dbLoader = new MapResourceLoader(getData()); Template t = gt.getTemplate("db:1", dbLoader); private Map getData() { Map data = new HashMap(); data.put("db:1", "${a}"); return data; } 

对于更复杂的模板资源来源,也能够自定义一个资源加载来完成,参考下一节

3.7. 自定义资源加载器

若是模板资源来自其余地方,如数据库,或者混合了数据库和物理文件,或者模板是加密的,则须要自定义一个资源加载器。资源加载器须要实现ResourceLoader类。以下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public interface ResourceLoader { /**  * 根据key获取Resource  *  * @param key  * @return  */ public Resource getResource(String key); /** 检测模板是否更改,每次渲染模板前,都须要调用此方法,因此此方法不能占用太多时间,不然会影响渲染功能  * @param key  * @return  */ public boolean isModified(Resource key); /**  * 关闭ResouceLoader,一般是GroupTemplate关闭的时候也关闭对应的ResourceLoader  */ public void close(); /** 一些初始化方法  * @param gt  */ public void init(GroupTemplate gt); /** 用于include,layout等根据相对路径计算资源实际的位置.  * @param resource 当前资源  * @param key  * @return  */ public String getResourceId(Resource resource, String key); } 

以下是一个简单的内存ResourceLoader

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class MapResourceLoader implements ResourceLoader { Map data; public MapResourceLoader(Map data) { this.data = data; } @Override public Resource getResource(String key) { String content = (String) data.get(key); if (content == null) return null; return new StringTemplateResource(content, this); } @Override public boolean isModified(Resource key) { return false; } @Override public boolean exist(String key) { return data.contain(key); } @Override public void close() { } @Override public void init(GroupTemplate gt) { } @Override public String getResourceId(Resource resource, String id) { //不须要计算相对路径 return id; } } 

init方法能够初始化GroupTemplate,好比读取配置文件的root属性,autoCheck属性,字符集属性,以及加载functions目录下的全部模板方法 如FileResourceLoader 的 init方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Override public void init(GroupTemplate gt) { Map<String, String> resourceMap = gt.getConf().getResourceMap(); if (this.root == null) { this.root = resourceMap.get("root"); } if (this.charset == null) { this.charset = resourceMap.get("charset"); } if (this.functionSuffix == null) { this.functionSuffix = resourceMap.get("functionSuffix"); } this.autoCheck = Boolean.parseBoolean(resourceMap.get("autoCheck")); File root = new File(this.root, this.functionRoot); this.gt = gt; if (root.exists()) { readFuntionFile(root, "", "/".concat(functionRoot).concat("/")); } } 

readFuntionFile 方法将读取functions下的全部模板,并注册为方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected void readFuntionFile(File funtionRoot, String ns, String path) { String expected = ".".concat(this.functionSuffix); File[] files = funtionRoot.listFiles(); for (File f : files) { if (f.isDirectory()) { //读取子目录 readFuntionFile(f, f.getName().concat("."), path.concat(f.getName()).concat("/")); } else if (f.getName().endsWith(functionSuffix)) { String resourceId = path + f.getName(); String fileName = f.getName(); fileName = fileName.substring(0, (fileName.length() - functionSuffix.length() - 1)); String functionName = ns.concat(fileName); FileFunctionWrapper fun = new FileFunctionWrapper(resourceId); gt.registerFunction(functionName, fun); } } } 

Resource类须要实现OpenReader方法,以及isModified方法。对于模板内容存储在数据库中,openReader返回一个Clob,isModified 则须要根据改模板内容对应的lastUpdate(一般数据库应该这么设计)来判断模板是否更改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public abstract class Resource { /**  * 打开一个新的Reader  *  * @return  */ public abstract Reader openReader(); /**  * 检测资源是否改变  *  * @return  */ public abstract boolean isModified(); 

参考例子能够参考beetl自带的ResourceLoader

3.8. 使用CompositeResourceLoader

组合加载器,能够包含多个已有的ResourceLoader,以下代码建立一个包含俩个文件和内存的ResourceLoader

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
FileResourceLoader fileLoader1 = new FileResourceLoader(path1); FileResourceLoader fileLoader2 = new FileResourceLoader(path2); Map data = getData(); // 根据id加载 MapResourceLoader mapLoader = new MapResourceLoader(data); CompositeResourceLoader loader = new CompositeResourceLoader(); loader.addResourceLoader(new StartsWithMatcher("http:").withoutPrefix(), fileLoader2); loader.addResourceLoader(new StartsWithMatcher("db:"), mapLoader); loader.addResourceLoader(new AllowAllMatcher(), fileLoader1); GroupTemplate gt = new GroupTemplate(loader, conf); Template t = gt.getTemplate("/xxx.html"); 

如上例子,groupTemplate从CompositeResourceLoader里加载/xxx.html,因为http:和db:前缀都不匹配,所以,将实际采用fileLoader1加载path1+/xxx.html,以下是xxx.html文件内容

1
2
3
4
<%
include("/xxx2.html"){} include("http:/xxx.html"){} %> 

第2行仍然是由fileLoader1加载,但第3行以http:前缀开头,所以将fileLoader2加载path2+/xxx.html.xxx.html内容以下

1
2
3
<%
include("db:1"){} %> 

由于以db:开头,所以会采用MapResourceLoader加载,内容是key为db:1对模板

3.9. 自定义错误处理器

错误处理器须要实现ErrorHandler接口的processExcption(BeetlException beeExceptionos, Writer writer);

  • beeExceptionos,模板各类异常

  • writer 模板使用的输出流。系统自带的并未采用此Writer,而是直接输出到控制台

自定义错误处理多是有多个缘由,好比

1 想将错误输出到页面而不是控制台

2 错误输出美化一下,而不是自带的格式

3 错误输出的内容作调整,如不输出错误行的模板内容,而仅仅是错误提示

4 错误输出到日志系统里

5 不只仅输出日志,还抛出异常。默认自带的不会抛出异常,ReThrowConsoleErrorHandler 继承了ConsoleErrorHandler方法,打印异常后抛出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class ReThrowConsoleErrorHandler extends ConsoleErrorHandler { @Override public void processExcption(BeetlException ex, Writer writer) { super.processExcption(ex, writer); throw ex; } } 

beetl 提供 ErrorInfo类来wrap BeetlException,转化为较为详细的提示信息,他具备以下信息

  • type 一个简单的中文描述

  • errorCode 内部使用的错误类型标识

  • errorTokenText 错误发生的节点文本

  • errorTokenLine 错误行

  • msg 错误消息,有可能没有,由于有时候errorCode描述的已经很清楚了

  • cause 错误的root 异常,也可能没有。

BeetlException 也包含了一个关键信息就是 resourceId,即出错所在的模板文件

3.10. 自定义安全管理器

全部模板的本地调用都须要经过安全管理器校验,默认须要实现NativeSecurityManager 的public boolean permit(String resourceId, Class c, Object target, String method) 方法

以下是默认管理器的实现方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class DefaultNativeSecurityManager implements NativeSecurityManager { @Override public boolean permit(String resourceId, Class c, Object target, String method) { if (c.isArray()) { //容许调用,但实际上会在在其后调用中报错。不归此处管理 return true; } String name = c.getSimpleName(); String pkg = c.getPackage().getName(); if (pkg.startsWith("java.lang")) { if (name.equals("Runtime") || name.equals("Process") || name.equals("ProcessBuilder") || name.equals("System")) { return false; } } return true; } } 

3.11. 注册全局共享变量

groupTemplate.setSharedVars(Map<String, Object> sharedVars)

3.12. 布局

布局能够经过Beetl提供的include,layout 以及模板变量来完成。模板变量能完成复杂的布局

  • 采用layout include

1
2
3
4
5
6
 <%
 //content.html内容以下: layout("/inc/layout.html"){%> this is 正文 .......... <%}%> 

如上一个子页面将使用layout布局页面,layout 页面内容以下

1
2
3
 <%include("/inc/header.html"){} %> this is content:${layoutContent} this is footer: 

layoutContent 是默认变量,也能够改为其余名字,具体请参考layout标签函数

全局变量老是能被布局用的页面所使用,若是布局页面须要临时变量,则须要显示的传入,如:

1
2
3
 <%
 var user= model.user; include("/inc/header.html",{title:'这是一个测试页面',user:user}){} %> 

这样,title和user成为全局变量,能被header.html 及其子页面引用到

  • 继承布局:采用模板变量和include

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 <%
        var jsPart = { %> web页面js部分 <%};%> <% var htmlPart = { %> web页面html部分 <%}; include("/inc/layout.html",{jsSecition:jsPart,htmlSection:htmlPart}){} %> 

layout.html页面以下:

1
2
3
4
5
6
7
8
<body>
<head> ${jsSection} </head> <body> ....... ${htmlSection} </body> 

3.13. 性能优化

Beetl性能已经很快了,有些策略能更好提升性能

  • 使用二进制输出,此策略可使模板在语法分析的时候将静态文本转化为二进制,省去了运行时刻编码时间,这是主要性能提升方式。但须要注意,此时须要提供一个二进制输出流,而不是字符流,不然性能反而降低

  • 使用FastRuntimeEngine,默认配置。 此引擎能对语法树作不少优化,从而提升运行性能,如生成字节码来访问属性而不是传统的反射访问。关于引擎,可能在新的版本推出更好的引擎,请随时关注。

  • 经过@type 来申明全局变量类型,这不能提升运行性能,但有助于模板维护

  • 自定义ResourceLoader的isModified必须尽快返回,所以每次渲染模板的时候都会调用此方法

为何Beetl性能这么好…………(待续)

3.14. 分布式缓存模板

Beetl模板引擎模板在同一个虚拟机里缓存Beetl 脚本。也能够将缓存脚本到其余地方,只要实现Cache接口,并设置ProgramCacheFactory.cache便可,这样GroupTemplate将从你提供的Cache中存取Beetl脚本

此功能未被很好测试

3.15. 定制模板引擎

Beetl在线体验(http://ibeetl.com:8080/beetlonline/)面临一个挑战,容许用户输入任何脚本作练习或者分享代码。但又须要防止用户输入恶意的代码,如

1
2
3
4
5
<%
for(var i=0;i<10000000;i++){ //其余代码 } %> 

此时,须要定制模板引擎,遇到for循环的时候,应该限制循环次数,譬如,在线体验限制最多循5次,这是经过定义替换GeneralForStatement类来完成的,这个类对应了for(exp;exp;exp) ,咱们须要改为以下样子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
class RestrictForStatement extends GeneralForStatement { public RestrictForStatement(GeneralForStatement gf) { super(gf.varAssignSeq, gf.expInit, gf.condtion, gf.expUpdate, gf.forPart, gf.elseforPart, gf.token); } public void execute(Context ctx) { if (expInit != null) { for (Expression exp : expInit) { exp.evaluate(ctx); } } if (varAssignSeq != null) { varAssignSeq.execute(ctx); } boolean hasLooped = false; int i = 0; for (; i < 5; i++) { boolean bool = (Boolean) condtion.evaluate(ctx); if (bool) { hasLooped = true; forPart.execute(ctx); switch (ctx.gotoFlag) { case IGoto.NORMAL: break; case IGoto.CONTINUE: ctx.gotoFlag = IGoto.NORMAL; continue; case IGoto.RETURN: return; case IGoto.BREAK: ctx.gotoFlag = IGoto.NORMAL; return; } } else { break; } if (this.expUpdate != null) { for (Expression exp : expUpdate) { exp.evaluate(ctx); } } } if (i >= 5) { try { ctx.byteWriter.writeString("--Too may Data in loop,Ignore the left Data for Online Engine--"); ctx.byteWriter.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } @Override public void infer(InferContext inferCtx) { super.infer(inferCtx); } } 

尽管上面代码很复杂,但其实是改写了原来的GeneralForStatement,将原来的24行while(true) 替换成for (; i < 5; i++) 用来控制最大循环,而且62行检测若是循环退出后,i等于5,则提示Too Many Data in Loop.

如今须要将此类替换原有的GeneralForStatement,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class OnlineTemplateEngine extends DefaultTemplateEngine { public Program createProgram(Resource resource, Reader reader, Map<Integer, String> textMap, String cr, GroupTemplate gt) { Program program = super.createProgram(resource, reader, textMap, cr, gt); modifyStatemetn(resource,program,gt); return program; } private void modifyStatemetn(Resource resource,Program program,GroupTemplate gt){ Statement[] sts = program.metaData.statements; StatementParser parser = new StatementParser(sts, gt, resource.getId()); parser.addListener(WhileStatement.class, new RestrictLoopNodeListener()); parser.addListener(GeneralForStatement.class, new RestrictLoopNodeListener()); parser.parse(); } } 

继承FastRuntimeEngine有所不一样,由于改引擎会copy出一个脚本作分析优化,所以,俩个脚本都须要作修改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class OnlineTemplateEngine extends FastRuntimeEngine { public Program createProgram(Resource resource, Reader reader, Map<Integer, String> textMap, String cr, GroupTemplate gt) { FilterProgram program = (FilterProgram)super.createProgram(resource, reader, textMap, cr, gt); modifyStatemetn(resource,program,gt); modifyStatemetn(resource,program.getCopy(),gt); return program; } } 
  • StatementParser 是关键类,他容许对模板的Program进行解析,并替换其中的Statement。parser.addListener 方法接受俩个参数,第一个是须要找的类,第二个是执行的监听器。

  • 能够参考在线体验的源码:http://git.oschina.net/xiandafu/beetlonline/blob/master/src/org/bee/tl/online/OnlineTemplateEngine.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class RestrictLoopNodeListener implements Listener { @Override public Object onEvent(Event e) { Stack stack = (Stack) e.getEventTaget(); Object o = stack.peek(); if (o instanceof GeneralForStatement) { GeneralForStatement gf = (GeneralForStatement) o; RestrictForStatement rf = new RestrictForStatement(gf); return rf; } else { return null; } } 

该监听器返回一个新的RestrictForStatement 类,用来替换来的GeneralForStatement。若是返回null,则不需替换。这一般发生在你仅仅经过修改该类的某些属性就能够的场景

完成这些代码后,在配置文件中申明使用新的引擎

1
ENGINE=org.bee.tl.online.OnlineTemplateEngine 

这样就完成了模板引擎定制。

3.16. 直接运行Beetl脚本

Beetl模板本质上会转化为Beetl脚原本执行,这点跟jsp转为servlet来执行相似。GroupTemplate提供方法能够直接执行Beetl脚本

  • public Map runScript(String key, Map<String, Object> paras) throws ScriptEvalError

  • public Map runScript(String key, Map<String, Object> paras, Writer w) throws ScriptEvalError

  • public Map runScript(String key, Map<String, Object> paras, Writer w, ResourceLoader loader) throws ScriptEvalError

key为资源名,paras为脚本的全局变量,w可选参数,若是执行脚本有输出,则输出到w里,loader参数可选,若是指定,则使用此laoder加载脚本

执行脚本完毕后,返回到Map里的值可能包含以下:

  • 模板的顶级的临时变量,key为临时变量名

  • return 值将返回到map里 ,key为return

以下脚本(此时就不须要脚本定界符了)

1
2
3
4
var a = 1; var b = date(); var c = '2'; return a+1; 

调用runScript后,map里将返回key分别为a,b,c,return。 值分别为1,当前日期,字符串'2,以及3.

4. Web集成

4.1. Web提供的全局变量

Web集成模块向模板提供web标准的变量,作以下说明

  • request 中的全部attribute.在模板中能够直接经过attribute name 来引用,如在controller层 request.setAttribute("user",user),则在模板中能够直接用${user.name} .

  • session 提供了session会话,模板经过session["name"],或者session.name 引用session里的变量

  • request 标准的HTTPSerlvetRequest,能够在模板里引用request属性(getter),如${request.requestURL}。

  • parameter 用户读取用户提交的参数。如${parameter.userId} (仅仅2.2.7以上版本支持)

  • ctxPath Web应用ContextPath

  • servlet 是WebVariable的实例,包含了HTTPSession,HTTPSerlvetRequest,HTTPSerlvetResponse.三个属性,模板中能够经过request.response,session 来引用,如 ${serlvet.request.requestURL};

  • 全部的GroupTemplate的共享变量

  • pageCtx是一个内置方法 ,仅仅在web开发中,用于设置一个变量,而后能够在页面渲染过程当中,调用此api获取,如pageCtx("title","用户添加页面"),在其后任何地方,能够pageCtx("title") 获取该变量。(仅仅2.2.7以上版本支持)

你能够在模板任何地方访问这些变量

4.2. 集成技术开发指南

Beetl默认提供了WebRender用于帮助web集成开发,全部内置的集成均基于此方法。若是你认为Beetl内置的各个web框架集成功能不够,你能够继承此类,或者参考此类源码从新写,其代码以下

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package org.beetl.ext.web; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; import java.util.Enumeration; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.beetl.core.GroupTemplate; import org.beetl.core.Template; import org.beetl.core.exception.BeetlException; /**  * 一般web渲染的类,将request变量赋值给模板,同时赋值的还有session,request,ctxPath  * 其余框架能够继承此类作更多的定制  * @author joelli  *  */ public class WebRender { GroupTemplate gt = null; public WebRender(GroupTemplate gt) { this.gt = gt; } /**  * @param key 模板资源id  * @param request  * @param response  * @param args 其余参数,将会传给modifyTemplate方法  */ public void render(String key, HttpServletRequest request, HttpServletResponse response, Object... args) { Writer writer = null; OutputStream os = null; try { // response.setContentType(contentType); Template template = gt.getTemplate(key); Enumeration<String> attrs = request.getAttributeNames(); while (attrs.hasMoreElements()) { String attrName = attrs.nextElement(); template.binding(attrName, request.getAttribute(attrName)); } WebVariable webVariable = new WebVariable(); webVariable.setRequest(request); webVariable.setResponse(response); webVariable.setSession(request.getSession()); template.binding("session", new SessionWrapper(webVariable.getSession())); template.binding("servlet", webVariable); template.binding("request", request); template.binding("ctxPath", request.getContextPath()); modifyTemplate(template, key, request, response, args); if (gt.getConf().isDirectByteOutput()) { os = response.getOutputStream(); template.renderTo(os); } else { writer = response.getWriter(); template.renderTo(writer); } } catch (IOException e) { handleClientError(e); } catch (BeetlException e) { handleBeetlException(e); } finally { try { if (writer != null) writer.flush(); if (os != null) { os.flush(); } } catch (IOException e) { handleClientError(e); } } } /**  * 能够添加更多的绑定  * @param template 模板  * @param key 模板的资源id  * @param request  * @param response  * @param args 调用render的时候传的参数  */ protected void modifyTemplate(Template template, String key, HttpServletRequest request, HttpServletResponse response, Object... args) { } /**处理客户端抛出的IO异常  * @param ex  */ protected void handleClientError(IOException ex) { //do nothing } /**处理客户端抛出的IO异常  * @param ex  */ protected void handleBeetlException(BeetlException ex) { throw ex; } } 

4.3. Serlvet集成

只须要在Servlet代码里引用ServletGroupTemplate就能集成Beetl,他提供了一个render(String child, HttpServletRequest request, HttpServletResponse response)方法。例子以下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); //模板直接访问users request.setAttribute("users",service.getUsers()); ServletGroupTemplate.instance().render("/index.html", request, response); } 

ServletGroupTemplate同其余web集成同样,将读取配置文件来配置,若是须要经过代码配置,能够在Serlvet listener里 ServletGroupTemplate.instance().getGroupTemplate()方法获取GroupTemplate

4.4. SpringMVC集成

须要作以下配置便可

1
2
3
4
5
6
<bean id="beetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"/> <bean id="viewResolver" class="org.beetl.ext.spring.BeetlSpringViewResolver"> <property name="contentType" value="text/html;charset=UTF-8"/> </bean> 

同其余集成方式同样,模板的配置将放在beetl.properties中。

若是想获取GroupTemplate,能够调用以下代码

1
2
3
BeetlGroupUtilConfiguration config = (BeetlGroupUtilConfiguration) this.getApplicationContext().getBean( "beetlConfig"); GroupTemplate group = config.getGroupTemplate(); 

Controller代码以下:

1
2
3
4
5
6
7
@RequestMapping(value = "/", method = RequestMethod.GET) public ModelAndView index(HttpServletRequest req) { ModelAndView view = new ModelAndView("/index"); //total 是模板的全局变量,能够直接访问 view.addObject("total",service.getCount()); return view; } 

4.5. SpringMVC集成高级

spring集成还容许注册被spring容器管理的Function,Tag等,也还容许配置多个视图解析器等功能

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<bean name="beetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"> <property name="configFileResource" value="/WEB-INF/beetl.properties"/> <property name="functions"> <map> <entry key="testFunction" value-ref="testFunction"/> </map> </property> <property name="functionPackages"> <map> <entry key="fp" value-ref="testFunctionPackage"/> </map> </property> <property name="tagFactorys"> <map> <entry key="html.output" value-ref="testTagFactory"/> <entry key="html.output2" value-ref="testTagFactory2"/> </map> </property> </bean> <bean name="testTagFactory" class="org.beetl.ext.spring.SpringBeanTagFactory"> <property name="name" value="testTag"/> </bean> <bean name="testTagFactory2" class="org.beetl.ext.spring.SpringBeanTagFactory"> <property name="name" value="testTag2"/> </bean> <bean name="beetlViewResolver" class="org.beetl.ext.spring.BeetlSpringViewResolver"> <property name="config" ref="beetlConfig"/> <property name="contentType" value="text/html;charset=UTF-8"/> </bean> 

如上图所示,BeetlGroupUtilConfiguration有不少属性,列举以下

  • configFileResource 属性指定了配置文件所在路径,若是不指定,则默认在classpath下

  • functions 指定了被spring容器管理的function,key为注册的方法名,value-ref 指定的bean的名称

  • functionPackages,指定了被spring容器管理的functionPackage,key为注册的方法包名,value-ref 指定的bean的名称

  • tagFactorys ,注册tag类,key是tag类的名称,value-ref指向一个org.beetl.ext.spring.SpringBeanTagFactory实例,该子类是一个Spring管理的Bean。属性name对应的bean就是tag类。须要注意,因为Tag是有状态的,所以,必须申明Scope为 "prototype"。如代码:

1
2
3
4
@Service
@Scope("prototype") public class TestTag extends Tag { } 
  • typeFormats: 同functions,参数是 Map<Class<?>, Format>,其中key为类型Class

  • formats:同functions,参数是 Map<String, Format>,其中key为格式化函数名

  • virtualClassAttributes 同functions,参数Map<Class<?>, VirtualClassAttribute>,其中key为类型Class

  • virtualAttributeEvals ,类型为List<VirtualAttributeEval>

  • resourceLoader,资源加载器 ,值是 实现ResourceLoader的一个Bean

  • errorHandler ,错误处理,值是实现ErrorHandler的一个Bean

  • sharedVars,同functions,类型是Map<String, Object>,能够在此设置共享变量

  • configProperties,类型是Properties,能够覆盖配置文件的某些属性

以下配置,指定了三个视图解析器,一个用于beetl页面渲染,一个用于cms,采用了beetl技术,另一个一些遗留的页面采用jsp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<bean name="beetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"> <property name="configFileResource" value="/WEB-INF/beetl.properties"/> </bean> <bean name="cmsbeetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"> <property name="configFileResource" value="/WEB-INF/cms-beetl.properties"/> </bean> <!-- Beetl视图解析器1 --> <bean name="beetlViewResolver" class="org.beetl.ext.spring.BeetlSpringViewResolver"> <!-- 多视图解析器,须要设置viewNames和order --> <property name="viewNames"> <list> <value>/template/**</value> </list> </property> <property name="suffix" value=".btl"/> <property name="contentType" value="text/html;charset=UTF-8"/> <property name="order" value="0"/> <!-- 多GroupTemplate,须要指定使用的bean --> <property name="config" ref="beetlConfig"/> </bean> <!-- Beetl视图解析器2 --> <bean name="cmsBeetlViewResolver" class="org.beetl.ext.spring.BeetlSpringViewResolver"> <!-- 多视图解析器,须要设置viewNames和order --> <property name="viewNames"> <list> <value>/cmstemplate/**</value> </list> </property> <property name="contentType" value="text/html;charset=UTF-8"/> <property name="order" value="1"/> <!-- 多GroupTemplate,须要指定使用的bean --> <property name="config" ref="cmsbeetlConfig"/> </bean> <!-- JSP视图解析器 --> <bean name="JSPViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 注意JSP的这个视图解析器order必须在最后 --> <property name="order" value="256"/> <!-- beetl配置不支持前缀,这不一样于jsp 和 freemaker --> <property name="prefix" value="/WEB-INF/"/> <property name="suffix" value=".jsp"/> <property name="contentType" value="text/html;charset=UTF-8"/> </bean> 

Beetl视图解析器属性同spring自带的视图解析器同样,支持contentType,order,prefix,suffix等属性。

注意视图解析器里属性viewNames,这个用于判断controller返回的path到底应该交给哪一个视图解析器来作。

  • 以/template开头的是beetlViewResolver来渲染。

  • 以/cmstemplate是交给cmsBeetlViewResolver渲染。

  • 若是都没有匹配上,则是jsp渲染

若是你想更改此规则,你只能增长canHandle方法指定你的逻辑了。详情参考org.springframework.web.servlet.view.UrlBasedViewResolver.canHandle

对于仅仅须要redirect和forward的那些请求,须要加上相应的前缀

  • 以"redirect:"为前缀时:表示重定向,不产生BeetlView渲染模版,而直接经过Servlet的机制返回重定向响应.redirect:前缀后面的内容为重定向地址,能够采用相对地址(相对当前url),绝对地址(完整的url),若是采用/开头的地址,会自动的在前面接上当前Web应用的contextPath,即contextPath为test的Web应用中使用redirect:/admin/login.html 实际重定向地址为 /test/admin/login.html

  • 以"forward:"为前缀时:表示转发,不产生BeetlView渲染模版。而是直接经过Servlet的机制转发请求(关于转发和重定向的区别,请自行查看Servlet API) forward:前缀后面的内容为转发地址,通常都是以/开头相对于当前Web应用的根目录

4.6. Jodd集成

须要配置web.xml,将全部请求交给jodd处理,参考:http://jodd.org/doc/madvoc/setup.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<filter>
        <filter-name>madvoc</filter-name> <filter-class>jodd.madvoc.MadvocServletFilter</filter-class> <init-param> <param-name>madvoc.webapp</param-name> <param-value>test.MyWebApplication</param-value> </init-param> <init-param> <param-name>madvoc.configurator</param-name> <param-value>test.MyAutomagicMadvocConfigurator</param-value> </init-param> </filter> <filter-mapping> <filter-name>madvoc</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 

MyWebApplication 和 MyAutomagicMadvocConfigurator 须要本身参照以下例子写一个,前者用来设置beetl做为视图渲染,后者配置Jodd不要扫描beetl struts集成里引用的struts类

1
2
3
4
5
6
7
8
9
public class MyAutomagicMadvocConfigurator extends AutomagicMadvocConfigurator { public MyAutomagicMadvocConfigurator(){ super(); //不扫描beetl 里jar文件里的action和result,不然,会扫描StrutsResultSupport不相干的class this.rulesJars.exclude("**/*beetl*.jar"); } } 
1
2
3
4
5
6
7
8
public class MyWebApplication extends WebApplication{ @Override protected void init(MadvocConfig madvocConfig, ServletContext servletContext) { //设置默认 madvocConfig.setDefaultActionResult(BeetlActionResult.class); } } 

最后,能够写Action了,浏览器输入/index.html,jodd将执行world方法,并渲染ok.html模板。若是你想配置GroupTemplate,正如其余集成框架同样,只须要写一个beetl.properties 便可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@MadvocAction
public class IndexAction { @Out String value; @Action("/index.html") public String world() { value = "Hello World!"; return "/ok.html"; } } 

4.7. JFinal集成

Beetl提供 JFinal 集成,使用BeetlRenderFactory ,经过以下注册便可使用beetl模板引擎

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import org.beetl.ext.jfinal.BeetlRenderFactory public class DemoConfig extends JFinalConfig { public void configConstant(Constants me) { me.setMainRenderFactory(new BeetlRenderFactory()); // 获取GroupTemplate ,能够设置共享变量等操做 GroupTemplate groupTemplate = BeetlRenderFactory.groupTemplate ; } 

业务逻辑代码:

1
2
3
4
5
6
7
8
9
        public void modify(){ int artId = getParaToInt(0, -1); setAttr("title", "修改文章"); List<Cate> cateLists = Cate.getAllCate(); //模板里访问cateLists,atr, setAttr("cateLists", cateLists); setAttr("art", Article.dao.findById(artId)); render("/modify.html"); } 

BeetlRenderFactory 默认使用FileResourceLoader ,其根目录位于WebRoot目录下,若是你须要修改到别的目录,能够设置配置文件,如

1
RESOURCE.root= /WEB-INF/template/ 

https://git.oschina.net/xiandafu/beetl-jfinal-sample 有完整例子,采用jfinal+beetl写的一个博客系统

4.8. Nutz集成

Nutz集成提供了 BeetlViewMaker ,实现了 ViewMaker方法,以下代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
        @At("/ctx") @Ok("beetl:ctx.btl") public Context withContext() { Context ctx = Lang.context(); Pager pager = dao.createPager(1, 20); pager.setRecordCount(dao.count(UserProfile.class)); List<UserProfile> list = dao.query(UserProfile.class, null, pager); ctx.set("pager", pager); ctx.set("list", list); return ctx; } 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<html>
<head> <title>Beetl&Nutz</title> </head> <body> <p>总共 ${list.~size}<p/> <% for(user in list){ %> <p>hello,${user.nickname};<p/> <%}%> <p>当前页${pager.pageNumber},总共${pager.pageCount}页<p/> </body> </html> 

4.9. Struts2集成

须要在struts2配置文件里添加result-types作以下配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<package name="default" namespace="/" extends="struts-default"> ....... <result-types> <result-type name="beetl" class="org.beetl.ext.struts2.Struts2BeetlActionResult" default="true" > <param name="contentType">text/html; charset=UTF-8</param> </result-type> </result-types> <action name="HelloWorld" class="com.beetl.struts.HelloWorld"> <result>/hello.html</result> </action> <action name="Ajax" class="com.beetl.struts.AjaxHtml"> <result>/table.html#table</result> </action> ........ </package> 

该类会根据struts配置文件获取模板,如上例的hello.html,并将formbean的属性,以及request属性做为全局变量传递给模板

郑重申明

鉴于struts2有安全漏洞,而官方补丁打法很消极,因此请谨慎使用Struts2,Beetl的安全性已经经过在线体验和多个使用Beetl的网站得以体现 一旦你的struts2网站被攻破,请先肯定是不是struts2 的问题

4.10. 直接Web中运行Beetl模板

对于web应用来讲,必须经过controller才能渲染模板,beetl也能够写完模板后,在未完成controller状况下,直接渲染模板 此方法既能够做为一般的全栈式开发人员使用,也能够用于前端人员单独开发模板用。

步骤以下:

  • 配置监听器,监听器指定对*.btl的请求进行监听(假定模板名字都是以btl.结尾)。

  • 实现监听器,该监听器继承父类 org.beetl.ext.web.SimpleCrossFilter,实现protected abstract GroupTemplate getGroupTemplate()方法。依据不一样的集成方式,好比你的环境是Servlet,则只须要调用ServletGroupTemplate.instance().getGroupTemplate(),若是是Jfinal,须要调用BeetlRenderFactory.groupTemplate等

  • SimpleCrossFilter 提供一些有用的方法,能够帮助你定制一些特性,能够参考源码了解

  • 置完成后,对于要测试的模板,能够新建一个对应的伪模型文件,好比要测试模板WebRoot/user/userList.html,能够新创建WebRoot/values/user/userList.html.var 。 values是监听器默认的伪模型的根目录

  • 编辑伪模型文件,对应于userList.html须要的全局变量,userList.html.var能够申明这些些变量

1
2
3
var proudct = {id:1,name:'测试产品',pic:'xxxx.jpg'}; var userList = [{id:2,name:'用户一'}]; var session= {admin:{id:1,name:'admin'}}; 
  • 经过浏览器直接访问http://ip:port/user/userList.html,监听器会预先执行userList.html.var,并将返回值做为模板的全局变量,传给userList.html

  • 能够将一些公共的变量放到WebRoot/values/common.var里(好比上面代码的session). 监听器会先执行common.var,而后再执行userList.html.var

直接访问模板前提是使用了伪模型,这与实际的项目采用的模型并不一致,所以当模板采用伪模型验证后,须要重启web应用,才能使用真正的模型去测试,不然,模板引擎会报错,这是由于beetl默认的FastRuntimeEngine会根据模型优化模板,对同一个模板不一样的模型会报错,除非采用DefaultTemplateEngine 或者页面申明类型变量是动态的。

4.11. 整合ajax的局部渲染技术

愈来愈多web网站依赖于ajax,如table的翻页,流行方式是浏览器发出ajax请求,后台处理后返回一个json,浏览器端将json数据拆开,拼成一条一条的行数据,而后生成dom节点,追加到表格里。 做为另一种可选技术,beetl支持局部渲染技术,容许后台处理返回的是一个完成的html片断,这样,前端浏览器能够直接将这个html片断追加到表格里。在我作的性能测试里,俩种方式性能差异不大(http://beetlajax.oschina.mopaas.com/)

好比模板index.html有不少动态内容,有动态生成的菜单,有右侧的top10,也有核心区域的表格,大概内容以下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<#menu/>
<#top10> ....</#top10> <div id="table-container" > <% //ajax片断开始 #ajax userTable: { %> <table> <tr><td width=100>id</td><td width=100>姓名</td></tr> <%for(user in users){%> <tr><td>${user.id}</td><td>${user.name}</td></tr> <%}%> </table> 当前页面<span id="current">${page!1}</span><span style="width:20px"></span> <a href="#"><span class="page">next</span></a> <a href="#" ><span class="page">pre</span></a> <% //ajax片断结尾 } %> 

#ajax 用于告诉告诉模板引擎,此处是个局部渲染标记,标记为"userTable",对于正常渲染视图"index.html"页面,#ajax标记会被忽略,table仍能获得正常渲染。若是渲染的视图是index.html#userTable,则模板只会渲染#ajax标记得模板片断,其余部分将忽略。关于完整例子,能够参考http://beetlajax.oschina.mopaas.com/

注意,Ajax片断本质上是从模版的ajax标记处开始渲染,所以,ajax须要的变量在模版里也必须是全局变量,若是你只是个局部变量,beetl会报出找不到变量,即便你binding了这个变量,beetl也认为这个是局部变量,如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
" >
<%
var tableData = paras.table;
#ajax userTable: {
for(user in tableData);
%>

<% //ajax片断结尾 } %> 

变量tableData是从paras里获取的,是个临时变量,所以就算你在后台binding了一个tableData,beetl 也不能识别。在渲染ajax片断的时候会报变量tableData找不到。改正的办法只能是让tableData全局变量。

返回Json好仍是返回html片断好?这个难以定论.

  • 从后台性能看,将模型序列化成json性能会比渲染模板性能更好,可是,json还须要前端从新解析生成最终html dom节点,这可能会延迟最终数据的现实效果。而返回的html片断就是已经生成好的dom

  • 从网络传入来看,json无疑更好的,html片断会有额外的html标记,css属性,以及有可能的js调用。传入流量有可能增长50%到100%。可是,对于web应用类,这些额外数据,并不算多。

  • 从开发效率来说,返回html片断的开发效率更高一些,由于渲染在后台操做,能够为所欲为的用模板语言来渲染,来取得后台数据,完成复杂渲染,而json就比较困难,能够说全部的json lib都没有完美的解决办法。

  • 从用户体验上来说,Beetl 采用ajax标记,混合了传统的模板渲染和ajax加载。用户进入页面即能看到数据,而经典的ajax json方式还须要异步加载,显示延迟。另外若是页面同时有多个ajax加载,则会对服务器形成很大的压力。

  • 关心服务器cpu消耗? 模板方式消耗更多的cpu,json方式则少点。可是俩者差距并不大。并且更多的web网站面临的状况是有富余的服务器CPU能力

  • 关心客户端CPU消耗? 过多的js无疑是客户端运行慢的主要缘由。若是采用经典的json方式,返回的json数据必然还须要通过js的计算和渲染。会影响客户机器cpu。

4.12. 在页面输出错误提示信息

2.2.3版本之后,新增长org.beetl.ext.web.WebErrorHandler,能够在web开发的时候在页面输出提示信息,在产品模式下载后台输出提示信息(经过配置属性ESOURCE.autoCheck= true来认为是开发模式),仅仅须要配置以下:

1
ERROR_HANDLER = org.beetl.ext.web.WebErrorHandler

5. 附录

5.1. 内置方法

5.1.1. 经常使用内置方法

  • date 返回一个java.util.Date类型的变量,如 date() 返回一个当前时间(对应java的java.util.Date); ${date( "2011-1-1" , "yyyy-MM-dd" )} 返回指定日期

  • print 打印一个对象 print(user.name);

  • println 打印一个对象以及回车换行符号,回车换号符号使用的是模板自己的,而不是本地系统的.若是仅仅打印一个换行符,则直接调用println() 便可

  • nvl 函数nvl,若是对象为null,则返回第二个参数,不然,返回本身 nvl(user,"不存在")

  • isEmpty 判断变量或者表达式是否为空,变量不存在,变量为null,变量是空字符串,变量是空集合,变量是空数组,此函数都将返回true

  • isNotEmpty 同上,判断对象是否不为空

  • has 变量名为参数,判断是否存在此全局变量,如 has(userList),相似于1.x版本的exist("userList"),但不须要输入引号了

  • assert 若是表达式为false,则抛出异常

  • trunc 截取数字,保留指定的小数位,如trunc(12.456,2) 输出是12.45

  • 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

  • pageCtx ,仅仅在web开发中,设置一个变量,而后能够在页面渲染过程当中,调用此api获取,如pageCtx("title","用户添加页面"),在其后任何地方,能够pageCtx("title") 获取该变量

5.1.2. 字符串相关方法

strutil方法对参数均不作空指针检测,你可自定义方法来覆盖这些内置的方法

  • strutil.startWith ${ strutil.startWith(“hello”,”he”) 输出是true

  • strutil.endWith ${ strutil.endWith(“hello”,”o”) 输出是true

  • strutil.length ${ strutil. length (“hello”),输出是5

  • strutil.subString ${ strutil.subString (“hello”,1),输出是“ello”

  • strutil.subStringTo ${ strutil.subStringTo (“hello”,1,2),输出是“e”

  • strutil.split ${ strutil.split (“hello,joeli”,”,”),输出是数组,有俩个元素,第一个是hello,第二个是joelli”

  • strutil.contain ${ strutil.contain (“hello,”el”),输出是true

  • strutil.toUpperCase ${ strutil.toUpperCase (“hello”),输出是HELLO

  • strutil.toLowerCase ${ strutil.toLowerCase (“Hello”),输出是hello

  • strutil.replace ${ strutil.replace (“Hello”,”lo”,”loooo”),输出是helloooo

  • strutil.format ${ strutil.format (“hello,{0}, my age is {1}”,”joeli”,15),输出是hello,joelli, my age is 15. 具体请参考http://docs.oracle.com/javase/6/docs/api/java/text/MessageFormat.html

  • strutil.trim 去掉字符串的尾部空格

  • strutil.formatDate var a = strutil.formatDate(user.bir,’yyyy-MM-dd’);

  • strutil.index var index = strutil.index("abc","a");返回 索引0

  • strutil.lastIndex var index = strutil.lastIndex("aba","a");返回索引2

5.1.3. 数组相关方法

  • array.range 返回数组或者Collection一部分,接受三个参数,第一个是数组或者Collection子类,第二,三个参数分别是起始位置

  • array.remove 删除某个数组或者Collection的一个元素,并返回该数组或者Collection.第一个是数组或者Collection子类,第二个参数是元素

  • array.add 向数组或者Collection添加一个元素,并返回该数组或者Collection。第一个是数组或者Collection子类,第二个参数是元素

  • array.contain 判断数组或者元素是否包含元素,若是包含,返回true。不然false。第一个是数组或者Collection子类,第二个参数是元素

  • array.toArray 转化成数组,如array.toArray(1,2,"a");

  • array.collection2Array 将java集合转化为数组 array.collection2Array([1,2,''])

5.1.4. 正则表达式相关方法

  • reg.match(str,regex) str为须要处理的字符串,regex是表达式

  • reg.replace(str,regex,replace),str为须要处理的字符串,regex是表达式,替换的字符串替换字符串

  • reg.find(str,regex) 返回找到的符合表达式的第一个字符串,不然返回空字符串

  • reg.findList(str,regex) 找到全部符合表达式的字符串,不然返回空列表

  • reg.split(str,regex),对字符串进行切分,返回列表

  • reg.split(str,regex,limit) 同上,limit是最多返回个数

5.2. 内置格式化方法

5.3. 内置标签函数

  • include include一个模板,如 :

1
 <%include("/header.html"){}%> 

若是想往子模板中传入参数,则能够后面跟一个json变量

1
   <%include("/header.html",{'user':user,'id',user.id}){}%> 

这样user,和id 能够在header.html被引用,并成为header.html的全局变量

(beetl1.2 也叫includeFileTemplate ,2.0仍然支持,但再也不文档里体现了)

  • layout 提供一个布局功能,每一个页面老是由必定布局,如页面头,菜单,页面脚,以及正文。 layout标签容许为正文指定一个布局,以下使用方式

    content.html内容以下:
1
2
3
4
5
6
 <%
 //content.html内容以下: layout("/inc/layout.html"){%> this is 正文 .......... <%%}%> 
layout.html 是布局文件,内容以下•
1
2
3
4
5
6
 <%
 <%include("/inc/header.html"){} %> this is content:${layoutContent} this is footer: <%%}%> 
运行content.html模板文件后,,正文文件的内容将被替换到layoutContent的地方,变成以下内容
this is header
this is content:this is 正文
............
this is footer:

若是想往layout页面传入参数,则传入一个json变量,以下往layout.html页面传入一个用户登陆时间

1
2
3
4
5
6
 <%
  layout("/inc/header.html",{'date':user.loginDate,'title':"内容页面"}){%> this is 正文 .......... <%%}%> 

若是layoutContent 命名有冲突,能够在layout第三个参数指定,如

1
2
3
4
5
6
7
 <%

 layout("/inc/header.html",{'date':user.loginDate,'title':"内容页面"},"myLayoutContent"){%> this is 正文 .......... <%}%> 
  • cache 能Cache标签的内容,并指定多长时间刷新,如

1
2
3
<%:cache('key2',10,false){ %> 内容体 <%}%> 

须要指定三个参数,第一个是cache的Key值,第二个是缓存存在的时间,秒为单位,第三个表示是否强制刷新•,false表示不,true表示强制刷新 Cache默认实现org.beetl.ext.tag.cache.SimpleCacheManager. 你能够设置你本身的Cache实现,经过调用CacheTag. cacheManager= new YourCacheImplementation();

能够在程序里调用以下方法手工删除Cache:

1
2
3
4
5
public void clearAll(); public void clearAll(String key); public void clearAll(String... keys); 
  • includeJSP,能够在模板里包括一个jsp文件,如:

1
2
3
<%
includeJSP("/xxxx.jsp",{"key":"value"}){} %> 

key value 都是字符串,将以parameter的形式提供给jsp,所以jsp能够经过request.getParameter("key")来获取参数

主要注意的是,这个标签并不是内置,须要手工注册一下

1
groupTemplate.registerTag("incdlueJSP",org.beetl.ext.jsp.IncludeJSPTag.class); 

5.4. 性能优化的秘密

Beetl2.0目前只完成了解释引擎,使用解释引擎好处是能够适用于各类场景,性能测试代表,Beetl2.0引擎是Freemaker的4-6倍,跟最好的 的编译引擎性能相比,也相差只有30%百分点。为何Beetl能跑的如此之快呢,简单的说,有以下策略

  • 优化IO输出,容许使用字节直接输出,模板中的静态文本事先转化为字节

  • encode优化,对于number类型,输出一般是.toString 转化成String,而后encode输出,这中间浪费了大量的资源,Beetl实现了encode,输出一步到位

  • Context 采用一维数组,语言里的Context一般采用Map实现,每次进入{} ,就新增一个child Map,尽管map很快,但不够快。也有其余模板语言采用二位数组提升性能,Beetl是经过固定大小一维数组来维护模板的Context,所以访问更快,也避免了Map和二维素组的频繁建立。其实除了此处,beetl不少地方都不采用Map来维护key-value, 而都采用数组索引,以追求性能极限

  • 字节码访问属性,经过反射获取性能比较慢,就算JVM有优化,但优化效果也不肯定。Beetl经过字节码生成了属性访问类,从而将属性访问速度提升了一个数量级

  • 类型推测:Beetl 是强制类型的,所以预先知道类型,能够对模板作一些优化而省去了动态判断类型的时间

  • 使用数组Buffer,避免频繁建立和销毁数组

  • 编译引擎将模板编译成类,会产生大量的类,虚拟机很难对这些作优化。而解释引擎只有几十个固定的类,虚拟机容易优化

相关文章

5.5. 性能测试对比

performance.jpg

performance2.png

5.6. Beetl 开发团队

做者

  • 闲.大赋:

助手

  • 做死模式:核心代码开发

  • 一粟蜉蝣:核心代码开发和版本发布

代码捐助者

  • 逝水fox :出色完成spring集成

  • kraken: 集合方法等扩展

  • 西安玛雅牛:复合加载器

  • 级?!: beetl扩展,crossMVC

  • orangetys: beetl插件

  • Oo不懂oO: beetl插件

  • 原上一颗草:Beetl早期使用者。

  • 龙图腾飞 ,WebErrorHandler,用来开发模式在 web上显示错误而不是控制台

  • nutz: nutz 集成和MapResourceLoader

  • 天方地圆 :提供正则方法

文档校验

  • 九月

  • Daemons

相关文章
相关标签/搜索