Beetl模板语言使用指南
---李家智 2013-1-30(新年首发版)
1.2. Beetl
可靠么?性能如何?.......................................................................... 3
2.2. 控制语句和占位符号................................................................................... 6
2.11. 直接调用class方法和属性......................................................................... 14
3.3. 允许优化,超越其他模板引擎................................................................... 21
感谢老婆和儿子,有时候容忍我回到家后还继续写代码
感谢公司和同事,都这么忙,还支持我完成Beetl
感谢使用Beetl的陌生朋友,亲~没有你们,Beetl也不可能做到最好
Beetl是Bee
Template language,Bee译为忙碌的人,意指忙碌中国的开发人员。目前版本1.2.0,整个大小415K,它具有如下特性:
1 非常简单:它的语法是javascript一个子集,只有少量的大家熟悉的符号。任何了解java,或者javascript的人,都能快速学会。如果从未用过任何模板语言,用Beetl是非常很合适的.不需要在学习一种的新的语言语法。语法可以参考在线学习网站http://223.4.25.163/beetlonline/
2 功能齐全:beetl具有目前流行的模板引擎所支持的功能,如具有 freemarker
所提供的绝大部分功能(参考 附录freemarker功能对比)。并且,并有有些独特功能是这些有10年以上历史模板引擎所不具备的。
3 性能卓越:在优化模式(包括动态编译成class和采用byte输出)下运行,每秒渲染模板数高于其他模板引擎,而消耗的系统资源低于其他模板引擎。(参考附录Freemarker性能比较)第三方评测结果看,性能是Freemarker的2-4倍
4 提供一系列其他模板语言没有提供的功能,如自定义占位符号,控制语句符号,模板变量,虚拟属性,自定义函数,标签tag等,安全输出等。它们并不复杂,但有可能解决你在使用别的模板语言时候遇到的一些不便捷的问题。
5 可以支持模板的单独开发和测试,即在MVC架构中,即使没有M和C部分,也能开发和测试模板。
6 同时支持较为松散的MVC和严格的MVC,如果在模板语言里嵌入计算表达式,复杂条件表达式,以及函数调用有干涉业务逻辑嫌疑,你可以禁止使用这些语法。关于这一点,可以参考strictly enforces model-view separation
7 能与Serlvet,Spring MVC,Struts2,JFinal 等web框架结合起来
作为模板语言,你可以用于任何适合在MVC的地方。如代码生成,或者超大规模访问量的互联网,电商的动态web界面,在功能上和性能上它完全能代替目前流行的Freemarker,Velocity。如果有任何问题,可以访问QQ群219324263,或者发送邮件到xiandafu@126.com
因为Beetl是基于antlr实现语法解析的,因此如果你仅仅对antlr感兴趣,beetl仍然可以作为你的一个重要参考
Beetl已经有不少使用者,有电子商务系统,也有企业内部管理系统,也有大型门户网站,经过过了一年多的使用和改进,beetl已经很稳定可靠了,如下式Beetl的单元测试。
Beetl的性能也非常不错,针对流行的Freemarker,做了单项和整体测试,每一项都能超过Freemarker。
Beetl目前每周下载量超过70个(含文档下载),已知的能公开联系方式的使用者如下但不限于如下
MR.周: thinking_chou@126.com
YQ:115649963
半兽人:http://my.oschina.net/sqhua/blog
原上一棵草:1086306
杨献明:http://my.oschina.net/gtd
周磊:http://my.oschina.net/b1412
kyle1970:http://my.oschina.net/kyle1970
Green:http://software-lgl.blogspot.com.au/
點點…愛護:http://my.oschina.net/hdjwhy/blog
大连-凭海临风:http://my.oschina.net/dlpinghailinfeng/
小小:http://my.oschina.net/u/927334
黎明伟:limingwei@mail.com
草根很忙:mintelyu@126.com
奋斗:http://www.riaway.com/
麦田:http://276708050.qzone.qq.com/
ddr 老北: http://my.oschina.net/hoodoon/blog
package org.bee.tl.samples; import org.bee.tl.core.GroupTemplate; import org.bee.tl.core.Template; public class Helloworld { public static void main(String[]
args) throws Exception { String
input = "hello,${name}!"; GroupTemplate
group = new GroupTemplate();/1 Template
template = group.getStringTemplate(input); /2 template.set("name", "beetl");/3 String
output = template.getTextAsString();/4 System.out.println(output); } } |
1
用空的构造函数创建 GroupTemplate创建一个模板,能接收一个字符串模板。 更常见的是用模板文件根目录初始化GroupTemplate。如GroupTemplate group =
new GroupTemplate(new File(“c:\templateroot”)),能接收一个模板文件创建模板
2
模板是String,通过GroupTemplate的getStringTemplate创建一个模板类Template。“${” “}”是模板里的占位符号。里面的表达式计算后将输出。在此例子里,name是一个变量
如果模板是一个文件,则需要getFileTemplate,传入一个相对root的路径来创建一个模板类,如
GroupTemplate group = new
GroupTemplate(new File(“c:/templateroot”))
Template template =
group.getFileTemplate(“/user/userList.html”);
GroupTemplate会解析c:/templateroot/user/userList.html
3
template.set("name", "beetl") 是定义模板变量,set方法允许 字符串-对象作对为参数.字符串为模板里的变量名,因此字符串必须合乎java变量名规则,如字母开头,不能有空格等。 通过set方法设置变量后,可以再模板里任何地方直接引用。
如果需要引用对象的属性,则用小数点,如${user.name},如果对象是个数组或者List集合,可以用[索引],如${user.friends[0]},如果对象是Map集合, 使用[key],key为任何对象,如${books[‘thinking in java’].author}
4 调用template.getTextAsString() 或者template.getText(Writer)或者 template.getText(OutputStream os) 都可以获得模板渲染的结果(如果设置允许byte直接输出,则只能用OutputStream,参考高级特性允许byte直接输出)。本例子中,输出是hello,beetl!
Beetl默认情况下,模板采用<%作为控制语句开始,%>作为控制语句结束
<% for(user in userList){ %> hello,${user.name} <%}%> |
默认情况下,占位符号使用"${"作为开始和"}"结尾占位符号
${users[index]} |
然而,Beetl支持自定义控制语句和占位符号,以适应不同类型模板文件,如果有你喜欢的风格,譬如html模板中,使用<!--: -->,则可以采用如下定义
public static void main(String[] args) { GroupTempate group = new GroupTemplate(root);
group.setStatementStart("<!--:");
group.setStatementEnd("-->");
group.setPlaceholderStart("${");
group.setPlaceholderStart("}");
// 或者可以直接调用 File child = …..
Template template =group.getFileTemplate(child);
String output = template.getTextAsString(); } |
此代码可以解析如下模板文件
<!--:
var name=”lijz”; var
greeting = “morning”; --> <p>Hello
${name} ${greeting~}</p> |
Beetl使用者常用的控制语句定界符号有
l <% %>
l <? ?>
l @ 和回车(设置定界符号位null,即可),如
@for(user in userList){
hello,${user.name}
@}
l <!--:
--> 或者 <!--# -->
l #: 和 回车
Beetl用户常使用的占位符号主要是”${}” 或者 “$ $”,”^
^”
以下内容如果不做特殊说明,控制语句使用<% %>和占位符号采用${}
Beet有俩种变量,一种是通过template.set(...)中传入的,这个是只读全局变量,在模板中任何一处,包括子模板等都可以被引用,但不能更改,另外,Beetl也允许定义临时变量,(在模板页面,使用临时变量并不值得推荐,但Beetl能支持),如下所示
<%var
name='lijz',loopCount=100+other ,girlName;%> |
关键字var是必须的,这点不同于javascript
Beelt中得变量同javascript不一样,没有自己的作用域,这点跟java类似,如下会报错,“变量已经定义”。早起版本支持javascript作用域,但显然这并不是一个好特性
<%var
name=’lijz’,i=1;
%> <%if(i>=1){%> <%var name='lucy';%> Hello,${name} <%}%> |
Beetl 支持定义Json变量,虽然此功能并不是很实用,推荐复杂的数据结构,还是要再控制层设置比较好
<% var
usersList=[{“name”:”lijz”,”age”:18},{“name”:”lucy”,”age”:16}];%> 她得名字是 ${userList[1][“name”]} |
Beetl将上述Json变量对应于java的Map或者List,如上例子则是一个List,包含了三个Map,第一个Map的name健值对应于“lijz”
Json的key值也可以不用单双引号,如上例,也可以写成
<% var
usersList=[{name:”lijz”,age:18},{name:”lucy”,age:16}];%>
对于Beetl中出现的整形或者浮点型,对应于java中BeeNumber,是一个支持长精度型的类.beetl并不试图wrap java对象,这不同于别的模板语言,但为了支持长精度运算,模板语言中的数字型都用BeeNumber代表
变量命名规则同javascript或者java,但不允许以俩个下划线开头"__",这是因为以此开头的多为Beetl内部的一些临时变量
属性引用是模板中的重要一部分,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类型
Beetl支持类似javascript的算术表达式和条件表达式,如+ - * / % 以及(),如下例子
<%:var a=1,b=2,c=3; %> the result
is ${(a+b)*c-0.75} |
注:算数表达式和逻辑表达式无需担心计算精度问题,beetl底层用BeeNumber来实现,支持长精度计算
Beetl支持类似Javascript,java的条件表达式
如>, <,
== ,!=,>= , <= 以及 !,如下例子
<% var
a=1,b=2,c=3;if((b-c)>=1){ %> Hello
big! <%}else{%> :( ,small! <%}%> |
判断变量是否为null,同js一样,如 if(a==null)
判断变量(全局变量)是否存在,可以用exist函数,如if(exist(“a”)),也可以使用安全输出特性,如
If(a!’’==’’) 如果a不存在,或者a为null,或者a是空字符串,均返回true。详细请参考安全输出
Beetl 支持for
in 循环格式,和while循环,以及break,continue,return (实际上可以出现在任何地方),如下例子
//java代码 tempalte.set("userList",userList); //模板 总共
${userList.~size} <%for(user in userList){%> ${user_index} . Welcome ${user.name}! <%}%> |
循环中可以使用数组,List 或者 Map的子类,当使用Map子类的时候,如下例子
<% var
softMap={'beetl':'very good ','freemarker':'very
good!'};%> Compare
the ${softMap.~size} soft ware as follows <%for(soft in softMap){%> ${soft_index}
/ ${soft_size} . ${soft.key} is ${soft.value} <%}%> |
key 既是Map里的key值
value 既是Map里的Value
如果循环中,需要引用当前索引,变量名加上后缀“_index”,代表当前循环的计数器,从0开始。在循环体里还可以变量名加上后最_size表示长度
Beetl 也可以在集合变量名后加上"!" 用以安全输出,即如果集合为null,则也不进入循环体,如
<%for(soft in softMap!){%> ................. <%}%> |
Beetl也支持while循环,其格式是while(条件表达式 ){},如下例子,循环4次输出
<% var a = 1; while(a<5){ %> ${a} <% a=a+1; } %> |
Beetl 支持同javascript,java一样的if 语句,和
switch. 如下例子
<%var
isGirl = true;%> <%if(isGir){%> 姑娘,你好 <%}else{%> 小伙子,你好 <%}%> |
Switch 和
case 可以使用任何表达式,下列中,switch 将变量score与case
中得各个常量比较,如果没有找到,则执行default
<%var
score = 1;%> <%switch(score){%> <%case 1:{%> Get 1 score! <%break;}%> <%default:{%> Default here <%} }%> |
Beetl内置了少量实用函数,可以在Beetl任何地方调用,一般情况是在占位符里调用。
如下例子是调用date 函数,不传参数情况下,返回当前日期
Today is ${date()} |
Beetl允许用户自定义函数以适合特定领域使用,请参考高级用法。也欢迎有人把他认为能公用的函数发给我,我将作为核心函数放入beetl里
几乎所有的模板语言都支持格式化,Beetl也不列外,如下例子Beetl提供的内置日期格式
<%var
date = date(); %> Today is
${date,dateFormat="yyyy-MM-dd"}. Today is
$date,dateFormat$ |
格式化函数只需要一个字符串作为参数放在=号后面,如果没有为格式化函数输入参数,则使用默认值,dateFormat格式化函数默认值是local
Beetl允许用户自定义格式化函数以适合特定领域,请参考高级用户,也欢迎有人把他认为能公用的格式化函数发给我,我将作为核心函数放入beetl里
默认情况,是不允许直接调用对象的方法的,必须先调用groupTemplate.enableNativeCall();
${@user.getMaxFriend(“lucy”)} |
可以调用instance的public方法和属性,也可以调用静态类的属性和方法 ,需要加一个@指示此调用是直接调用class
目前Beetl不支持连续调用Class方法和属性,如下列是非法的
@com.xxxx.constants.Order.getMaxNum().intValue()
Beetl默认情况使用 org.bee.tl.core.DefaultErrorHandler 来处理语法解析错误和运行时刻的错误,在解释执行的认情况下,会显示如下信息
l 错误行号
l
错误原因以及异常栈
l 错误的关键字
l 以及错误上下3行
l 错误所在的模板文件
如下所示
Template t = new BeeTemplate("<%:if(!isGirl){var
c=1;}%>"); //使用严格MVC限制,因此不允许变量定义
|
关于如何自定义错误处理,请参考高级用法
如果是在编译执行,则显示的信息没有错误关键字,这是因为已经编译成class,java报错不支持错误关键字
指令格式为: DIRECTIVE 指令名 指令参数(可选)
Beetl目前只支持安全输出指令,分别是
DIRECTIVE SAFE_OUTPUT_OPEN
;
DIRECTIVE SAFE_OUTPUT_CLOSE
;
前者打开安全输出功能,此指令后的所有表达式都具有安全输出功能,
后者是关闭安全输出功能。详情参考安全输出
安全输出是任何一个模板引擎必须重视的问题,否则,将极大困扰模板开发者。Beetl中,如果要输出的模板变量为null,则beetl将不做输出,这点不同于JSP,JSP输出null,也不同于Feemarker,如果没有用!,它会报错.
模板中还有俩中情况会导致模板输出异常
l 有时候模板变量并不存在(譬如子模板里)
l 模板变量为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
l 还有一种情况很少发生,但也有可能,输出模板变量发生的任何异常,如变量内部抛出的一个异常
这需要使用格式${!(变量)},这样,在变量引用发生任何异常情况下,都不作输出,譬如
${!(user.name)},,beetl将会调用user.getName()方法,如果发生异常,beetl将不会忽略此异常,继续渲染
如下是预编译后的代码
try{ out.write(user.getName()); }catch(Exception ex){ } |
值得注意的是,在变量后加上!不仅仅可以应用于占位符输出(但主要是应用于占位符输出),也可以用于表达式中,如:
<%
var k = user.name!'N/A'+user.age!;
%>
${k}
如果user为null,则k值将为N/A
在有些模板里,可能整个模板都需要安全输出,也可能模板的部分需要安全输出,使用者不必为每一个表达式使用!,可以使用beetl的安全指示符号来完成安全输出
如:
<% DIRECTIVE SAFE_OUTPUT_OPEN; %> ${user.wife.name} 模板其他内容,均能安全输出…… <% //关闭安全输出 DIRECTIVE SAFE_OUTPUT_CLOSE; %> |
Beetl不建议每一个页面都使用DIRECTIVE SAFE_OUTPUT_OPEN,这样,如果如果真有不期望的错误,不容易及时发现,其次,安全输出意为着beetl会有额外的代码检测值是否存在或者是否为null,性能会略差点
Beetl采用javascript一样的注释风格,即单行使用"//" ,多行可以使用/* */
如下代码
<% var a = 3 ; // 单行注释 /* 这是多行注释 */ var b = 2; /* 以下内容页被注释了 .................. %> 内容........... <% */ %>
|
Beetl现在暂时不支持占位符的注释
对齐: 我发现别的模板语言要是做到对齐,非常困难,Beetl你完全不用担心,比如velocty,stringtemlate,freemarker例子都出现了不对齐的情况,影响了美观,Beetl完全无需担心输出对齐
包含其他模板文件:在Beetl中,这不是一个特殊的功能,通过调用函数includeFileTemplate,或者includeStringTemplate都可以实现,前者是包含一个文件模板,后者是将一个string模板作为输入。详细使用请参考高级用法
Escape:可以使用\ 做escape 符号,如\$monkey\$ 将作为一个普通的文本,输出为$monkey$.再如为了在钱后加上美元符号(占位符恰好又是美元符号)可以用这俩种方式hello,it's
$money$\$, 或者Hello,it's
$money+"\$"$ 。如果要输出\符号本生,则需要用俩个\\,这点与javascript,java 语义一致.
空值策略: 如果一个变量为空,则输出的时候默认为空字符串,也可以制定输出
${u.wife.name!"N/A"} |
如果u 为空,或者u.wife为空,输出都是"N/A"
标签:类似jsp的标签Tag,允许你将模板文件中的一段文件内容作为输入,经过函数操作,变成特定的输出,如下标签
<%includeFileTemplate("/footer.html"){ %> This is
static footer,will be replaced by fotter.html <%}%>
|
则模板文件里等于号后的字符串将被以此替换.
表达式计算: org.bee.tl.core.SimpleRuleEval .扩展了BeeTemplate,可以用来计算表达式,如下代码
SimpleRuleEval eval = new
SimpleRuleEval("a+15"); eval.set("a", 3); int o = eval.calcInt(); 此时o为 18 |
表达式可以是任何计算表达式或者条件表达式,如(12+5)*3,或者a>12&&a<=18
其他方法还有
calc()返回Object,(对于算数计算,Object为BeeNumber类型)
calcInt() 返回Int
calcBoolean() 返回boolean
calcDouble() 返回double
其他详细参考API
无论你是在学习使用Beetl,还是在项目中使用Beetl,好的习惯是正确初始化GroupTemplate,并通过GroupTemplate获得Template,如下示例代码(也可以参考3.2来创建GroupTemplate)
import
org.bee.tl.core.GroupTemplate; public class GroupTemplateUtil { static GroupTemplate group = new GroupTemplate(new File("/home/template")); static { group.setPlaceholderStart("<%"); group.setPlaceholderEnd("%>"); group.setStatementStart("${"); group.setStatementEnd("}"); group.enableNativeCall(); //1 group.enableChecker(10); //2
/* 其他配置。。。。。。。 */ //最后允许编译成class
group.enableOptimize(); //3 addCommonFunction(); //4 addCommonFormat(); //5 } public static GroupTemplate getGroup (){ return group; } public static void addCommonFunction(){ } public static void addCommonFormat(){ } } |
1 运行直接调用java类
2 每10秒检查一下模板文件是否更新,如果未调用,或者设置为0,则不检查更新
3 允许优化成class代码运行
4 增加项目自定义方法
5 增加项目自定义的格式化函数
然后你可以在代码里调用
Template t =
GroupTemplateUtil .getGroup ().getFileTemplate("/exp/string_add_template.html"); T.set("user",new User()); String str =
t.getTextAsString(); |
注意:groupTemplate. enableOptimize应该在其他配置选项后再调用,因为此选项要根据前面的配置来初始化groupTemplate
GroupTemplate有很多选项,更为可以通过Config类来完成创建GroupuTemplate,Config默认会先装载 /org/bee/tl/core/beetl-default.properties,然后如果classpath下存在beetl.properties,再装载或者覆盖其属性。beetl-default.properties 内容如下
#######默认配置 DELIMITER_PLACEHOLDER_START=${ DELIMITER_PLACEHOLDER_END=} DELIMITER_STATEMENT_START=<% DELIMITER_STATEMENT_END=%> NATIVE_CALL = TRUE COMPILE_CLASS=FALSE DIRECT_BYTE_OUTPUT
= FALSE TEMPLATE_ROOT= TEMPLATE_CHARSET
= GBK TEMPLATE_CACHE_CHECK_PERIOD
= 2 TEMPLATE_CLASS_FOLDER= ERROR_HANDLER = org.bee.tl.core.DefaultErrorHandler MVC_STRICT = FALSE #内部使用 DEBUG=FALSE #######默认配置结束 #######性能最佳配置开始 #COMPILE_CLASS=true #DIRECT_BYTE_OUTPUT
= true ########性能最佳配置结束 #######严格MVC配置开始 #MVC_STRICT =
TRUE #NATIVE_CALL =
FALSE ########严格MVC配置结束 #######编译成class的其他选项开始,未完全测试,暂时不支持 #COMPILE_CLASS_KEEP_SOURCE=FALSE #OPTIMIZE_COMPILE_LATTER=FALSE #OPTIMIZE_COMPILE_WORKER_NUM=2 #######编译成class的其他选项结束 |
如下例子是beetl单元测试的配置
Config config = new Config(); config.load("/beetl-test.properties"); home = System.getProperty("user.dir") + File.separator + "template" + File.separator; config.put(Config.TEMPLATE_ROOT, home); GroupTemplate base = config.createGroupTemplate(); |
默认情况下,Beetl采用解释执行,性能略低于其他模板语言,且同其他模板语言一样,消耗了较大的系统资源。Beetl1.0版本后可以在运行时编译成class,并且通过运行时刻推测出其模板变量的java类型,通过杜绝反射操作从而获得最好的性能
只需要调用groupTemplate.enableOptimize();
默认情况下,所以预编译的类都将放在user.home的.bee目录下,如下示例
如果想要保存在指定的目录,可以在调用enableOptimize前调用groupTemplate.setTempFolder(String
path),传入路径
目前并不是所有的模板都能优化成class代码。请参考代码优化了解如何编写模板代码。但无论如何,如果优化失败,beetls将会解释执行。
注意:新版本中默认不生成java源代码,如果想保持生成的java代码,则需要如下初始化GroupTemplate
Map config = new HashMap();
config.put(GroupTemplate.OPTIMIZE_KEEP_SOURCE, true);
group.enableOptimize(config);
如果你确定输出的字符集总是固定的,比如总是GBK,那么,允许byte输出将对性能进一步提供质的优化。加速渲染速度以及降低对CPU的消耗。这是因为,byte直接输出,省去了模板中文本的转码过程。允许byte字节输出,性能会提供一倍,需要做的仅仅是如下俩个设置:
groupTemplate.enableDirectOutputByte();
再输出的时候,提供OutputStream作为输出流,如web开发里
protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); Template
t = ServletGroupTemplate. PortalService
portalService = new PortalService(); List<News>
list = portalService.getNews(News.GNENERAL_NEWS); t.set("newsList", list); try { t.getText(response.getOutputStream()); }
catch (BeeException e) { // TODO Auto-generated catch block e.printStackTrace(); } } |
Beetl允许提供自定义函数以适合特定业务需求,自定义函数需要实现org.bee.tl.core.Function。如下定义了一个date函数仅仅返回一个java.util.Date实例
public class DateFunction implements Function { public Date call(Object[] paras,Context ctx) {
return new Date(); } public static void main(String[] args) throws IOException{
GroupTemplate group = new GroupTemplate();
group.registerFunction("date", new DateFunction());
Template t = group.getStringTemplate("today is
$date()$");
System.out.println(t.getTextAsString()); } } |
注册的方法名可以带".",如下
group.registerFunction("date.now",
new DateFunction());则在模板里,可以使用today is
${date.now()}.
另外一种自定义beetl方法的是使用GroupTemplate.registerFunctionPackage(String
packageName,Object o);
Beetl将找到o所属的所有public 非Static方法,并依次按照packageName+"."+方法名 作为beetl方法名,如下例子
group.registerFunctionPackage("string", new Object(){ /** * @param str * @param from d对于数字型,beetl里都使用BeeNumber * @param to * @return * @throws Exception */ public String sub(String str,BeeNumber from,BeeNumber to) throws Exception{ if(str==null){ throw new Exception("error
paramter"); } return str.substring(from.intValue(), to.intValue()); } public void print(String
str,Context ctx){ ByteWriter w = (Writer)ctx.getVar("__pw"); try { w.write(str); }
catch (IOException e) { throw new
RuntimeException(e.getMessage()); } } }); |
如上注册了俩个方法,分别是string.sub方法以及string.print函数。
Beetl允许提供自定义格式化函数,用于格式化输出。 格式化函数需要实现org.bee.tl.core.Format
public class DateFormat extends Format { public Object
format(Object data,String pattern){
SimpleDateFormat sdf = null;
if(pattern==null){
sdf = new SimpleDateFormat();
}else{
sdf = new SimpleDateFormat(pattern);
}
return sdf.format(data); } public static void main(String[]
args)throws IOException {
GroupTemplate group = new GroupTemplate();
group.registerFunction("now", new DateFunction());
group.registerFormat("df", new DateFormat());
Template t = group.getStringTemplate("today is
$now(),df=’yyyy-MM-dd’$");
System.out.println(t.getTextAsString()); } } |
其中,也可以直接用today is ${now(),shortDate} 如果没有=号,format(Object data, String pattern) 中的pattern
为空
注册的格式化函数名可以带".",如下:
group.registerFormat("date.short",
new DateFormat());则在模板里,可以使用today is ${now(),date.short}.
模板变量定义和赋值跟其他变量没有区别,唯一不同的时他允许的内容是一段模板,他的值是模板所对应的字符串(字节模式下是字节数组),格式如下
var varibaleName = {….}.,模板变量可以一旦定义好后,可以用在其任何地方,譬如重复使用模板变量,将模板变量用在子模板里:
<% var a = { var k= 123; %> ${k}. <% }; %> ${a} |
如上例,a是一个模板变量,结果是”123.”
使用模板变量一个常见的地方是更加灵活的布局
<% var part1 = {
%> 这是内容一 <% }; %> <% var part2 = {
%> 这是内容二 <% }; %> inlcudeFileTemplate("/common/_layout.html",{"part1":part1,"part2":part2}) |
如果设置了严格MVC,则以下语法将不在模板文件里允许,否则将报出STRICK_MVC 错误
定义变量,为变量赋值,如var a = 12是非法的
算术表达式 如${user.age+12}是非法的
除了只允许布尔以外,不允许逻辑表达式和方法调用 如if(user.gender==1)是非法的
方法调用,如${subString(string,1)}是非法的
Class方法和属性调用,如${@user.getName()}是非法的
严格的MVC,非常有助于逻辑与视图的分离,特别当逻辑与视图是由俩个团队来完成的。如果你嗜好严格MVC,可以调用groupTemplate.enableStrict()
无需为java对象定义额外的属性用于辅助显示,虚拟属性可以轻易做到,如Beetl为java.util.Collection 定义的一个虚拟属性size,用于表示集合大小
group.registerVirtualAttributeEval(new VirtualAttributeEval(){ public Object
eval(Object o,String attributeName,Context ctx){
if(attributeName.equals("size")){ return ((Collection)o).size();
}else{
throw new IllegalArgumentException();
}
} public boolean isSuppoert(Class
c,String attributeName){
if(Collection.class.isAssignableFrom(c)&&attributeName.equals("size")){
return true;
}else{
return false;
} } }); |
这样,所以Collection子类都有虚拟属性size。$userList.~size$
输出userList集合长度
实现虚拟属性,必须实现接口俩个方法,一个是isSupport,这让Beetl用于找到VirtualAttributeEval,eval方法用于计算虚拟属性
所谓标签,即允许处理模板文件里的一块内容,功能等于同jsp tag。如下{}的内容在beetl运行的时候将会被删除
<%del(){%> This
content will be deleted <%}%> |
自定义标签org.bee.tl.core.Tag,需要实现requriedInput,用于告诉Beetl,是否需要先渲染文本体。
setInput 是把渲染的结果传回给标签函数
getOutput 最后用于返回文本处理函数的结果
如下是Beetl提供的内置的del文本处理函数实现
public class DeleteFunction
extends Tag{ public String
getOutput(){
return ""; } @Override public boolean requriedInput(){
return false; } } |
可以通过父类属性args,获取输入参数,详细可以参考API
由于标签将会处理标签内的渲染结果,如果在使用byte输出的话,最好继承ByteSupportTag,同时实现getOutput(),getOutputAsByte()俩个接口,具体用法可以查看源码。如果是允许直接输出,Beetl将会调用getOutputAsByte () 而不是getOutput(),然而,如果你继承Tag,也可以的,只是byte输出的性能将在此处会打一点折扣.因为beetl判断只实现了Tag接口,就会调用getOutput()方法,将得到输出再转一次Byte作为渲染结果。
详情参考IncludeFileTemplateTag和 LayoutTag的实现。
Beetl不直接支持宏定义,但可以间接通过内置的includeFileTemplate 标签来做到,这正如JSP做的那样,如下是一个print.html文件,将定义个宏
Debug info is <% println(object);%> |
则在任何文件中,都可以使用includeFileTemplate 使用此宏
<% inclucdeFileTemplate("/print.html",{"object":realValue}){}%> |
大多数模板语言都会输出额外的空格或者回车,JSP也如此,freemaker还专门有一删除多余空格的函数,在beetl中,是不需要此功能的
上图来源于南磊翻译的中文版Freemarker,说是以上代码会有过多的换行(br),必须使用删除多余空行函数才能使模板输出达到我
们想得样子。Beetl没有此额外函数做这事情,因为Beetl自动就能分辨出这些额外空行。
为什么beetl无需担心额外空行呢,其实很简单,beetl碰到只有beetl语句的行,无论是前有空格还是后有回车,都会忽略的,看过beetl 在优化模式下生成的class代码就很容易能看出来。
但在一种情况下,模板尾部,Beetl是无非判断是否空行的,如下:
${name} <% …………
beetl语句%> |
在第一行占位符name后面有个回车,beetl无法判断这个回车是否是用户需要的回车还是仅仅为了其后的控制语句。目前情况下,beetl认为用户需要输出空格行,因此,模板的输出会在尾部多出一个空格。
groupTemplate..setErrorHandler(h)用于设置你自定义的错误处理,譬如,默认情况下,错误处理会显示错误所在行,错误原因,以及模板上下3行的内容,如果你不希望客户看到你的模板内容,你可以自定义错误处理,请参考org.bee.tl.core.DefaultErrorHandler
如果groupTemplate.setErrorHandler,传入null,则表示不希望groupTemplate处理错误,其后模板的任何错误都将抛出,如下例子:
try{ Template
template = …… ; template.set(…..); t.getText(writer) }catch(BeeException
bee){ DefaultErrorHandler h = new DefaultErrorHandler (); h.processExcption(bee); //or 打印到前台,h.processExcption(bee,writer); throw new
YourExcepion(bee.getMessage(),bee); } |
字符串模板通常用于CMS系统,将模板页面生成静态页面,此时字符串模板来源于CMS的数据库。通常情况下,生成静态页面可以不缓存模板,但如果对性能也有要求,可以缓存字符串模板,可以调用
|
Input是字符串模板,key是此模板缓存的唯一标示,必须是合法的java类名(这样在允许编译成class的时候,此key就是class类名)。如果需要删除缓存,则调用如下语句即可
groupTemplate.removeTemplateCache(key); |
除了getStringTemplate方法外,还可以调用 getReaderTemplate,输入是java.io.Reader
模板语言必须支持布局,否则就不是一个好的模板语言,Beetl支持用标签函数layout做简单布局,也支持联合使用模板变量和include标签函数来做复杂布局。
Layout布局请参考8.3的标签函数
复杂布局参考3.7的模板变量
在后台没有准备好模型的情况下,如何验证模板是否正确呢,可以采用json变量来代替pojo的java对象来完成测试,beetl扩展包提供了org.bee.tl.ext.SimpleTemplateTestUtil来帮助测试
如下代码:
String input = "this is template,${user.name},${sessions['userId']}"; String json = "var user = {'name':'joel'},sessions={'userId':'12345'};"; Writer w = new StringWriter(); SimpleTemplateTestUtil util = new SimpleTemplateTestUtil(input, json, w); util.run(); System.out.println(util.isOk()); System.out.println(w); |
输出是
true this is template,joel,12345 |
可以看到尽管后台模型(user对象,和sessions会话还没有准备好),模板仍然能够运行测试,如果模板有错误,错误也将输出Writer里
为了能在Spring
MVC中使用Beetl,必须配置ViewResolver,如下
<bean id="beetlConfig" class="org.bee.tl.ext.spring.BeetlGroupUtilConfiguration"
init-method="init"> <property name="root" value="/"/> <property name="optimize" value="true"/> <property name="nativeCall" value="true"/> <property name="check" value="2"/> </bean> <bean id="viewResolver" class="org.bee.tl.ext.spring.BeetlSpringViewResolver"> </bean> |
Root属性告诉Beetl 模板文件未WebRoot的哪个目录下,通常是/ ,默认是/
optimize 属性允许优化,预编译成class。默认是true
nativeCall 运行本地调用,默认是true
check 是每隔多少秒检测一下文件是否改变,设置较短时间有利于开发,在线上环境,设置为0,则不检查模板更新,默认是2秒
其他属性还有
tempFolder:预编译生成的源文件以及class的位置,默认是WebRoot/WEB-INF/.temp 目录下
占位符指定:statementStart,statementEnd,placeholderStart,placeholderEnd 默认分别是 <%
%> ${ }
charset:模板字符集,默认是GBK
开发者也可以继承BeetlGroupUtilConfiguration,实现initOther方法,为GroupTemplate增加更多的特性,如下代码
public class MyBeetlGroupConfigration extends BeetlGroupUtilConfiguration { Protected void initOther(){ group.registerFunction(......) } }
|
在Spring MVC中,任何在ModelMap中的变量都可以直接在Beetl中引用,在Session中的变量,需要使用session["变量名"]
如下HelloWorldController 代码
@Controller @SessionAttributes("currUser") public class HelloWorldController
{ @RequestMapping("/hello") public ModelAndView
helloWorld(ModelMap model ) { String
message = "Hello World, Spring 3.0!"; model.addAttribute("name","joel"); model.addAttribute("currUser","libear"); return new ModelAndView("/hello.html", "message", message); } } |
则在模板中,访问name,message,currUser分别采用如下方式
${name},${message},${session["currUser"]}
除了用户设置的得变量外,其他能访问的变量还有
ctxPath : web应用的上下文环境路径
servlet: 包含了request,response,session
只需要在Struts配置文件里增加如下配置,就可以使用Beetl,此时Beetl的属性仍然是读取classpath的beetl.properties.如果没有,就使用默认
· <result-types> · <result-type name="beetl" class="org.bee.tl.ext.struts2.Struts2BeetlActionResult" default="true"/> ·
</result-types> |
在视图层,可以直接访问Action定义好的属性,也可以访问如下变量。
session,可以获取session会话
request,是HTTPRequest
ctxPath,是request.getContextPath()
如果你需要初始化GroupTemplate,如增加一些自定义方法等,可以创建一个心累继承Struts2BeetlActionResult,并覆盖public void initGroupTemplate(GroupTemplate group)
如果你还需要在模板渲染前做一些处理,可以覆盖public void addCommonProperty(Template t, HttpServletRequest
req,HttpServletResponse rsp)
Jfinal 是国内很有潜力的Web开发框架,Beetl可以很容易作为其框架的视图层技术。代码如下
import
org.bee.tl.ext.jfinal.BeetlRenderFactory public class DemoConfig extends JFinalConfig { public void configConstant(Constants
me) { me.setDevMode(true); me.setMainRenderFactory(new
BeetlRenderFactory()); } } |
默认情况下BeetlrednerFacotry会装载classpath 路径/ beetl.properties 的配置,配置里未指定属性TEMPLATE_ROOT的值,这BeetlrednerFacotry默认的模板根目录在WebRoot/WEB-INF/template目录下。如果指定了,譬如TEMPLATE_ROOT=/beetl/template 则模板跟目录在WebRoot/WEB-INF/beetl/template.
在控制层,通过setAttr方法设置的变量,可以再在模板文件中直接引用,模板中还内置了如下变量
session,可以获取session会话
request,是HTTPRequest
ctxPath,是request.getContextPath()
如下模板例子
Hello,${session[“user”]} <% for(order in orderList){} %> <image src=${ctxPath}/images/user?${@request.getParameter(“action”)}> |
可以通过调用静态方法BeetlRenderFactory. groupTemplate 来获取groupTemplate,如:
BeetlRenderFactory. groupTemplate.registerFunction……
无论在何种框架下使用Beetl,都必须保证GroupTemplate是单例,且被正确初始化,所以Beetl扩展包提供了一种在Servlet中使用Beetl的例子,使用用户监听器初始化GroupTemplate,如下代码
public class MyTestListener implements ServletContextListener { public void
contextInitialized(ServletContextEvent arg0) { ServletGroupTemplate.intance().init(arg0.getServletContext()); } public void
contextDestroyed(ServletContextEvent arg0) { } } |
ServletGroupTemplate 位于扩展包org.bee.tl.ext 下,此类将从web.xml读取如下变量初始化GroupTemplate:
GroupTemplate.Root属性告诉Beetl 模板文件未WebRoot的哪个目录下,通常是/ ,默认是/
GroupTemplate.Optimize 属性允许优化,预编译成class。默认是true
GroupTemplate.NativeCall 运行本地调用,默认是true
GroupTemplate.Check 是每隔多少秒检测一下文件是否改变,设置较短时间有利于开发,在线上环境,设置为0,则不检查模板更新,默认是2秒
GroupTemplate.DirectByteOutput 设置为true,允许byte输出从而提升性能,默认是false
其他属性还有
GroupTemplate.TempFolder:预编译生成的源文件以及class的位置,默认是WebRoot/WEB-INF/.temp 目录下。但是由于部署方式以及web服务器不一样,如使用war方式部署,Beetl不能从ServletContext得到正确的绝对路径。你最好看看是否是在默认目录下,否则,请指定一个绝对路径。
占位符指定:GroupTemplate.StatementStart,GroupTemplate..StatementEnd,GroupTemplate.PlaceholderStart,GroupTemplate.PlaceholderEnd 默认分别是 <%
%> ${ }
GroupTemplate.Charset:模板字符集,默认是GBK
因此Web.xml文件看起来像这样
<listener> <listener-class>com.MyTestListener</listener-class> </listener> <context-param> <param-name>GroupTemplate.Root</param-name> <param-value>/</param-value> </context-param> <context-param> <param-name>GroupTemplate.Optimize</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>GroupTemplate.NativeCall</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>GroupTemplate.Check</param-name> <param-value>2</param-value> </context-param> |
只需要在Servlet中调用ServletGroupTemplate.instance().getTemplate(child,request,response)就可以获取Template
如下代码
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException { //
TODO Auto-generated method stub request.getSession().setAttribute("user",
"joelli"); Template
t = ServletGroupTemplate.intance().getTemplate("/hello.html",
request, response); t.set("message",
"hello"); try
{ response.setCharacterEncoding("UTF-8"); t.getText(response.getWriter()); }
catch (BeeException e) { //既然没有设置错误处理器,GroupTemplate将使用默认处理,此异常不会被抛出 } } |
getTemplate将会获取child对应的模板,并还做如下赋值操作
将Session赋值给session,将ContextPath赋值给ctxPath, 以及所有request中的属性都赋值给Template,因此可以在模板使用
${session["user"]} ,${ctxPath},${meesage} 来访问Servelt中赋值的变量
注意,如果要获取groupTemplate,可以调用ServletGroupTemplate. instance ().getGroup() 获取
扩展包现在提供的功能有限,主要是提供常用扩展,以及作为例子参考。如果你有很好的扩展,可以参与到扩展包的开发来
函数名称 |
使用说明 |
date |
返回一个java.sql.Date类型的变量,如 ${date('2011-1-1','yyyy-MM-dd')} 返回指定日期 |
print |
打印一个对象 <% print(user.name);%> |
println |
打印一个对象以及回车换行符号,回车换号符号使用的是模板本身的,而不是本地系统的 <% print(user.name);%> |
printf |
格式化输出,如 <%printf("Hello, %s. Next year, you'll be %d", name, age)%> 具体格式请参考 |
nvl |
函数nvl,如果对象为null,则返回第二个参数,否则,返回自己 ${nvl(user,"不存在"} |
debug |
在控制台输出debug指定的对象以及所在模板文件中的行数,采用 System.out.println("<line "+line+">"+debugObject.toString); <% debug("whatever");%> |
exist |
判断全局变量是否存在(不能判断临时变量) <%if(exist(“user”,”sessions”)){……}%> |
assert |
如果表达式为false,则抛出异常,异常信息可以第二个参数指定 assert( exist(“userList”) ),或者 assert( exist(“userList”),”缺少用户信息” ); Beetl将不再渲染模板,退出 |
trunc |
截取数字,保留指定的小数位,如 ${trunk(12.456,2)} 输出是12.45 ${trunk(12.456,0)} 输出是12 ${trunk(12.456)} 输出是12 |
decode |
一个简化的if else 结构,如 ${decode(a,1,"a=1",2,"a=2","不知道了")} 如果a是1,这decode输出"a=1",如果a是2,则输出"a==2", 如果是其他值,则输出"不知道了" |
函数名称 |
使用说明 |
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),输出是“el” |
strutil.split |
${ strutil.split (“hello,joeli”,”,”),输出是数组,有俩个元素,第一个是hello,第二个是joelli” |
strutil.contain |
${ strutil.split (“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 |
函数名称 |
使用说明 |
dateFormat |
日期格式化函数,如 ${date,dateFormat='yyyy-Mm-dd'} 等于符号后的参数也可以没有,则使用本地默认来做格式化 如 ${date,dateFormat} |
numberFormat |
${0.345,numberFormat="##.#%"} 输出是 34.5%,具体请参考文档 http://docs.oracle.com/javase/6/docs/api/java/text/DecimalFormat.html |
函数名称 |
使用说明 |
deleteTag |
此标签体的内容将不作输出 |
includeFileTemplate |
include一个模板,如 <%includeFileTemplate("/header.html"){}%> 如果想往子模板中传入参数,则可以后面跟一个json变量,如 include一个模板,如 <%includeFileTemplate("/header.html",{'user',user:'id',user.id}){}%> |
inlucde |
同上 |
layout |
提供一个布局功能,每个页面总是由一定布局,如页面头,菜单,页面脚,以及正文 layout标签允许为正文指定一个布局,如下使用方式
content.html内容如下: <%layout("/org/bee/tl/samples/layout.html"){%> this is 正文 .......... <%%}%> layout.html 是布局文件,内容如下·
this is header 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页面传入一个用户登录时间 <%layout("/org/bee/tl/samples/layout.html",{'date':user.loginDate}){%> this is 正文 .......... <%%}%> |
cache |
能Cache标签的内容,并指定多长时间刷新,如 <%:cache('key2',10,false){ %> 内容体 <%%> 需要指定三个参数,第一个是cache的Key值,第二个是缓存存在的时间,秒为单位,第三个表示是否强制刷新·,false表示不,true表示强制刷新 Cache默认实现org.bee.tl.ext.cache.SimpleCacheManager. 你可以设置你自己的Cache实现,通过调用CacheTag. cacheManager= new YourCacheImplementation(); 可以在程序里调用如下方法手工删除Cache: public void clearAll(); public void clearAll(String key); public void clearAll(String... keys); |
参考了Freemarker官方文档(2.3.19) 第一列是官方文档目录,第二列是表示beetl中是否具有同样的功能
通过下列表格,可以看出绝大部分功能,beetl都是支持的。
功能 |
是否支持 |
备注 |
1.1 简介 |
支持 |
|
1.2模板 + 数据模型 = 输出 |
支持 |
|
1.3 数据模型一览 |
支持 |
|
1.4.1 简介 |
支持 |
|
1.4.2 (.1--.4) 指令示例 |
支持 |
Beetl中,采用includeFileTemplate标签来include一个文件。Beetl中还支持switch/case |
1.4.2.5 处理不存在的变量 |
支持 |
不支持询问变量是否存在,但可以通过扩展函数来支持 |
2.1 基本内容 |
支持 |
|
2.2.1 类型 简介 |
大部分支持 |
很少使用的节点类型不支持 |
2.2.2 标量 |
支持 |
|
2.2.3 容器 |
支持 |
|
2.2.4 方法和函数 |
支持 |
可以通过扩展函数来间接支持 |
2.2.5 其它(节点类型) |
不支持 |
|
3.1 总体结构 |
支持 |
|
3.2 指令 |
支持 |
|
3.3 表达式 |
支持 |
集合操作不支持,但可以通过扩展函数来实现集合操作。 截取字符串也不是内置的,但可以通过扩展函数来实现,如str.subString |
3.3.3.1 字符串 |
支持 |
|
3.3.3.2 数字 |
支持 |
|
3.3.3.3 布尔值 |
支持 |
|
3.3.3.4 序列 |
支持 |
采用js语法的json格式支持。但不包括支持start..end 这样的方式 |
3.3.3.5 哈希表 |
支持 |
|
3.3.4 检索变量 |
支持 |
|
3.3.5 字符串操作 |
不支持 |
此语法很奇怪,只能说freemaker复杂了语法 |
3.3.5.2 获取一个字符 |
支持 |
通过扩展函数支持 |
3.3.6.1 序列操作 连接 |
支持 |
通过扩展函数支持 |
3.3.6.2 序列切分 |
支持 |
通过扩展函数支持 |
3.3.7 哈希表操作
连接 |
支持 |
|
3.3.8 算数运算 |
支持 |
|
3.3.9 比较运算 |
支持 |
|
3.3.10 逻辑操作 |
支持 |
|
3.3.11 内建函数 |
支持 |
|
3.3.13 处理不存在的值 |
支持 |
通过扩展函数支持 |
3.3.13.1 默认值 |
部分支持 |
|
3.3.13.2 检测不存在的值 |
支持 |
|
3.3.14 括号 |
支持 |
|
3.3.15 表达式中的空格 |
支持 |
|
3.3.16 操作符的优先级 |
支持 |
无数字范围 这个概念 |
3.4 插值 |
支持 |
|
4.1 自定义指令 (宏) |
支持 |
间接通过标签等支持 |
4.1.4 嵌套内容 |
支持 |
Beetl中主要用在layout标签里 |
4.1.5 宏和循环变量 |
支持 |
|
4.2 在模板中定义变量 |
支持 |
|
4.3 命名空间 |
部分支持 |
通过扩展函数支持, |
4.4 空白处理 |
支持 |
Beetl中不需要此额外功能 |
4.5替换(方括号)语法 |
支持 |
|
还是以常用模板为准(大小6K),循环渲染50000次,需要时间(毫秒为单位)如下
Beetl1.2M1 解释执行方式(普通模式),运行三次,分别 1356,1365,1348
Beetl1.2M1 编译执行方式(普通模式),运行三次,分别 913,922,905
Freemarker 分别是
1155,1130,1122
Beetl1.2M1 允许字节流优化,解释方式执行三次,分别是 587,605,610
Beetl1.2M1 允许字节流优化,编译方式执行三次,分别是 385,355,370
简而言之,对beetl做运行时编译,byte输出设定后,渲染5万次6K的模板,性能如下
Freemarker |
Beetl |
1140毫秒 |
370毫秒 |
及时不对beetl做任何优化,性能也与Freemarker持平.如下是单项测试的性能比较,数据不再列出,没项性能测试都超过了Freemarker
更新时间 |
更新内容 |
2012-11-18 |
重构完成,发布了1.2beta版本 |
2012-10-24 |
文档完善和bug,修复了一个会产生多余空格的轻微bug |
2012-10-5 |
性能优化,添加错误处理文档说明 |
2012-9-25 |
测试模板,修复bug |
2012-9-23 |
字符串模板 |
2012-9-9 |
安全输出做了调整,可以应用于表达式中 |
2012-8-25 |
通过使用Config来简化创建GroupTemplate |
2012-6-10 |
Beetl1.2M1发布,性能优化,文本合并输出,二进制直接输出 |
2012-1-8 |
Beetl1.1发布,编译class完善 |
2011-10-1 |
Beetl1.0发布,运行中编译成class |
2011-6-1 |
Beetl的0.6一个初始化版本发布 |
2011-4-1 |
开始设计和开发beetl |
2010-12-1 |
Java模板引擎那么多,程序员必须用这些难用的模板引擎么,无法选择 |