1. 什么是 Beetl
Beetl 是 Bee Template language , Bee 译为忙碌的人,意指忙碌中国的开发人员。目前版本 1. 1 beta,大小约3 00 K5 它具有如下特性:
1 非常简单 :它的语法是 javascript 一个子集,只有少量的大家熟悉的符号。任何了解 java ,或者 javascript 的人,都能快速学会。如果从未用过任何模板语言,用 Beetl 是非常很合适的
2 功能齐全: beetl具有目前流行的模板引擎所支持的功能,如具备freemarker 所提供的绝大部分功能
3 性能卓越 : 在优化模式下运行, 每秒渲染模板数高于其他模板引擎,而消耗的系统资源低于其他模板引擎。
4 提供一系列其他模板语言没有提供的功能 ,如自定义占位符号,控制语句符号,虚拟属性,自定义函数,标签 tag等, 安全输出等。 它们并不复杂,但有可能解决你在使用别的模板语言时候遇到的一些不便捷的问题。
5 同时支持较为松散的 MVC 和严格的 MVC ,如果在模板语言里嵌入计算表达式,复杂条件表达式,以及函数调用有干涉业务逻辑嫌疑,你可以禁止使用这些语法。关于这一点,可以参考 strictly enforces model-view separation
6 能与Spring MVC 和 Struts2结合起来
1.1. Beetl 能为你做些什么
作为模板语言,你可以用于任何适合在 MVC 的地方。如 代码生成 ,或者 web 界面 ,
因为 Beetl 是基于 antlr 实现语法解析的,因此如果你仅仅对 antlr 感兴趣, beetl 仍然可以作为你的一个重要参考
关于 Beetl 性能 :
Beetl 目前渲染一个 7K 文件,内含少量控制语句和占位符,所需要时间是 1 毫秒,这是在我一个四年前的老机器上跑得,作为代码生成,你完全无需担心性能。
| Beetl(runtime,) | Freemarker(2.3.1.8) | Beetl 1.0(使用优化模式) | Velocity |
7K ( 1000 次) | 1050 | 790 | 560 |
|
注:优化模式即将模板编译成 class,目前测试性能表明性能高于freemarker,消费的系统资源低于freemarkder。
关于功能:
http://freemarker.sourceforge.net/fmVsVel.html 是一篇freemaker与velocity功能比较的文章,很幸运 Beetl能以简单易学,更易扩展的方式支持所有功能 。
下表是以此文章为基础做的比较
功能点 | Beetl | Freemarker | velocity |
Number and date suppor t | yes | yes | no |
Internationalization: | Yes,但不支持中文变量名 | yes | no |
Loop handling: | Ye s | yes | no |
Array handling on the template language level | yes | yes | no |
Macros | Yes | Yes | no |
Name-spaces: | No | yes | no |
Java-independent string, list, and map manipulations with built-in functions/operators: | yes | yes | no |
Expose typos and other mistakes in template | Yes, better | yes | no |
Advanced rendering control: | yes | yes | no |
Literals: | yes | yes | no |
Advanced white-space removal | No ,beetl不需要此额外功能 | yes | no |
Integration with other technologies: | yes | yes | yes |
Powerful XML transformation capabilities: | no | yes | no |
Advanced template metaprogramming: | No,不明白为啥有此需求 | yes | no |
function | No,觉得模板不需要 | yes | No |
自定义控制语句 | yes | no | no |
自定义占位符号 | yes | no | no |
严格MVC控制 | yes | no | no |
虚拟属性 | yes | no | no |
文本处理函数 | yes | yes | no |
自定以错误处理 Hanlder | yes | no | no |
2. 基本用法
2.1. Hello Beetl
package org.bee.tl.samples; import java.io.IOException; import org.bee.tl.core.BeeTemplate; public class HelloBeetl {
public static void main(String[] args) throws IOException { Template template = new BeeTemplate( "Hello, ${ name } " );// 1 template.set( "name" , "Beetl" );//2 String result = template.getTextAsString();//3 System. out .println(result); } }
|
1 用于 BeeTemplate 创建一个模板,此时使用的是一个字符串输入 , 输入也可以是 java.io.File 或者java.io.reader. 对于 beetl 来说,如果输入是文件,那将会缓存中间的解析结果而大幅度提升性能
2 定义变量, set 方法允许字符串 , 对象作为参数,如果需要引用对象的属性,则用小数点,如 $user.name$, 如果属性是个 List 集合,可以用 [ 索引 ], 如 $user.friends[0]$, 如果属性是 Map 集合,
使用 [key],key 为任何对象,如 $books[‘thinking in java’].author$
3 调用 template.getTextAsString() 或者 template.getText(OutputStream os) 都可以获得模板渲染的结果
2.2. 控制语句和占位符号
Beetl 默认情况下,采用 <% 作为控制语句开始, %> 作为控制语句结束
<% for(user in userList){ %> hello,$ { user.name } <% } %> |
默认情况下,占位符号使用 " $ {" 作为开始和 "}" 结尾占位符号
$ { users[index] } |
然而, Beetl 支持自定义控制语句和占位符号,以适应不同类型模板文件,如果有你喜欢的风格,譬如 jsp类似的, 则可以采用如下定义
public static void main(String[] args) { String input = ”…..” Template template =new BeeTemplate(input); template.setStatementStart("<%"); template.setStatementEnd("%>"); template.setPlaceholderStart(" ${ "); template.setPlaceholderStart(" } "); template.getTextAsString(); // 或者可以直接调用
} |
此代码可以解析如下模板文件
<% var name=”lijz”; var greeting = “morning”; %> Hello ${ name } ${ greeting~ } |
以下内容如果不做特殊说明,控制语句使用 <% %> 和占位符号采用 ${}
2.3. 定义变量
Beetl 允许定义变量,准确的说, 允许定义临时变量 (在模板页面,使用临时变量并不值得推荐,但Beetl能支持),如下所示
<% var name= ' lijz ', loopCount=100 +other ,girlName %> |
关键字 var 是必须的,这点不同于 javascript
Beelt 中得变量同 javascript 一样,有自己的作用域,如下模板输出为 ”lucy”
<% 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”] } |
变量命名规则同javascript或者 java ,但不允许以俩个下划线开头 "__", 这是因为以此开头的多为 Beetl 内部的一些临时变量
2.4. 算术表达式
Beetl支持类似 javascript 的算术表达式和条件表达式,如 + - * / % 以及(),如下例子
<% :var a=1,b=2,c=3; %> the result is $ { (a+b)*c-0.75 } |
注:算数表达式无需担心计算精度问题,beetl底层用BigDecimal实现。
2.5. 逻辑表达式
Beetl支持类似 Javascript,java 的条件表达式 如 > , < , == , != , >= , <= 以及 !, 如下例子
<% var a=1,b=2,c=3;if((b-c)>=1){ %> Hello big! <% }else{ %> :( ,small! <% } %> |
2.6. 循环语句
Beetl 支持 for in 循环格式,以及 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 _szie} . $ { soft.key } is $ { soft.value } <% } %> |
key 既是 Map 里的 key 值
value 既是 Map 里的 Value
如果循环中,需要引用当前索引 , 变量名加上后缀“_index”,代表当前循环的计数器,从0开始。在循环体里还可以变量名加上后最 _size表示长度
2.7. 条件语句
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 <% } }%> |
2.8. 函数调用
Beetl内置了少量实用函数,可以在 Beetl 任何地方调用,一般情况是在占位符里调用。
如下例子是调用 date 函数,不传参数情况下,返回当前日期
Today is $ {date()} |
Beetl允许用户自定义函数以适合特定领域使用,请参考高级用法。也欢迎有人把他认为能公用的函数发给我,我将作为核心函数放入 beetl 里
2.9. 格式化
几乎所有的模板语言都支持格式化, Beetl 也不列外,如下例子 Beetl 提供的内置日期格式
<% var date = now(); %> Today is $ { date, dateFormat ="yyyy-MM-dd" } . Today is $date, dateFormat $ |
如果没有为格式化函数输入参数,则使用默认值, dateFormat 格式化函数默认值是 local
Beetl允许用户自定义格式化函数以适合特定领域,请参考高级用户,也欢迎有人把他认为能公用的格式化函数发给我,我将作为核心函数放入 beetl 里
2.10. 直接调用 class方法和属性
默认情况,是不允许直接调用对象的方法的,必须先调用groupTemplate. enableNativeCall();
$@user.getMaxFriend(“lucy”)$ |
可以调用 instance的public方法和属性,也可以调用静态类的属性和方法 ,需要加一个@指示此调用是直接调用class
2.11. 错误处理
Beetl默认情况使用 org.bee.tl.core.DefaultErrorHandler 来处理语法解析错误和运行时刻的错误,默认情况下,会显示如下信息
l 错误行号
l 错误原因以及异常栈
l 错误的关键字
l 以及错误上下 3行
如下所示
Template t = new BeeTemplate(" <% :if(!isGirl){var c=1;} %> "); //使用严格MVC限制,因此不允许变量定义
|
关于如何自定义错误处理,请参考高级用法
2.12. 安全输出
Beetl允许占位符在有可能抛错的情况下忽略此错误,使用 !( ) 指示括号里的代码如果出错,系统将忽略此错误
比如
${!(user.wife,name)}
如果user 的 wife 属性为空,则将抛出空指针异常,如果使用安全输出指示,则系统忽略此异常。如下是预编译后的代码
try{ out.write(user.getWife().getName()); }catch(Exception ex){ }
|
注意,针对空值得情况,建议使用空值策略,参考(2.13 )
${user.wife,name!"N/A"}
如下是空值策略预编译后的代码
if(user!=null&&user.getWife()!=null){ |
2.13. 其他琐碎功能
对齐: 我发现别的模板语言要是做到对齐,非常困难 ,Beetl 你完全不用担心,比如 velocty , stringtemlate ,freemarker 例子都出现了不对齐的情况,影响了美观, Beetl 完全无需担心输出对齐
包含其他模板文件 :在 Beetl 中,这不是一个特殊的功能,通过调用函数 includeFT, 或者 includeST 都可以实现,前者是包含一个文件模板,后者是将一个 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, 允许你将模板文件中的一段文件内容作为输入,经过函数操作,变成特定的输出,如下标签replaceProperties
<% replaceProperties( ip,port ) { %> Server_ip= 127.0.0.1 Server_port= 8080 <% } %>
<% if(isProduct) { delNext(){ %> Debug_para1=..... Debug_para2=...... <% } %>
|
第一个例子中,如果在 java 代码中, tempalte.set("ip",targetIP),template.set("port",targetPort);
则模板文件里等于号后的字符串将被以此替换 .
第二个例子,如果在 java 代码中, template.set("isProduct",true), 则所有 debug 参数都将被删除。关于标签的概念,请参考高级用法
表达式计算: org.bee.tl.core . SimpleRuleEval . 扩展了BeeTemplate , 可以用来计算表达式 ,如下代码
SimpleRuleEval eval = new SimpleRuleEval("b+15"); eval.set("a", 3); int o = eval.calc Int (); 此时 o 为 18 |
表达式可以是任何计算表达式或者条件表达式,如(12+5)*3,或者 a>12&&a<=18
其他方法还有
calc()返回Object,(对于算数计算,Object为BeeNumber类型)
calcInt() 返回Int
calcBoolean() 返回boolean
calcDouble() 返回double
其他详细参考API
3. 高级用法
3.1. 总是使用 GroupTemplate
无论你是在学习使用 Beetl,还是在项目中使用Beetl,好的习惯是正确初始化GroupTemplate,并通过GroupTemplate获得Template,如下代码
import org.bee.tl.core.GroupTemplate; public class GroupTemplateUtil {
static GroupTemplate group = new GroupTemplate( "/home/template" ); static { group .setPlaceholderStart( "<%" ); group . setPlaceholderEnd ( "%>" ); group .setStatementStart( "${" ); group .setStatementEnd( "}" ); group .enableOptimize(); //1 group .enableNativeCall(); //2 group .enableChecker(10); //3 addCommonFunction (); //4 addCommonFormat (); //5
} public static GroupTemplate getGroup (){ return group ; } public static void addCommonFunction(){
}
public static void addCommonFormat(){
}
}
|
1. 允许优化成class代码运行
2 运行直接调用java类
3 每10秒检查一下模板文件是否更新
4 增加项目自定义方法
5 增加项目自定义的格式化函数
然后你可以在代码里调用
Template t = GroupTemplateUtil .getGroup () .getFileTemplate( "/exp/string_add_template.html" );
T.set("user",new User()); String str = t.getTextAsString();
|
3.2. 允许优化,超越其他模板引擎
默认情况下, Beetl采用解释执行,性能略低于其他模板语言,且同其他模板语言一样,消耗了较大的系统资源。Beetl1.0版本后可以在运行时预编译成class,获得最好的性能
需要调用groupTemplate .enableOptimize();
默认情况下,所以预编译的类都将放在 user.home的.bee目录下,如下示例
目前并不是所有的模板都能优化成class代码。请参考代码优化了解如何编写模板代码。但无论如何,如果优化失败,beetls将会解释执行。
3.3. 自定义函数
Beetl 允许提供自定义函数以适合特定业务需求,自定义函数需要实现 org.bee.tl.core.Function 。如下定义了一个now 函数仅仅返回一个 java.util.Date 实例
public class DateFunction implements Function { public Date call(Object... paras) { return new Date(); } public static void main(String[] args) throws IOException{ GroupTemplate group = new GroupTemplate(); group.registerFunction( "now" , new DateFunction()); Template t = group.getStringTemplate( "today is $now()$" ); System. out .println(t.getTextAsString()); }
} |
注册的方法名可以带".",如下
group.registerFunction("date.now", new DateFunction());则在模板里,可以使用today is
${date.now()}.
3.4. 格式化函数
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}.
3.5. 严格MVC 控制
如果设置了严格 MVC ,则以下语法将不在模板文件里允许,否则将报出 STRICK_MVC 错误
定义变量,为变量赋值
算术表达式
除了只允许布尔以外,不允许逻辑表达式和方法调用
Class方法和属性调用
如果你嗜好严格 MVC,可以调用groupTemplate . enableStrict ( )
3.6. 虚拟属性
无需为 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 方法用于计算虚拟属性
3.7. 标签
所谓标签,即允许处理模板文件里的一块内容,功能等于同 jsp tag。 如下 {} 的内容在 beetl 运行的时候将会被删除
<% del(){ %> This content will be deleted <% } %> |
自定义标签 org.bee.tl.core.T ag ,需要实现 requriedInput ,用于告诉 Beetl ,是否需要先渲染文本体。
setInput 是把渲染的结果传回给标签函数
getOutput 最后 用于返回文本处理函数的结果
如下是 Beetl 提供的内置的 del 文本处理函数实现
public class DeleteFunction extends Tag { public String getOutput(){ return "" ; } @Override public boolean requriedInput(){ return false ; }
}
|
可以通过父类属性 args, 获取输入参数,详细可以参考 API
3.8. 空格处理
大多数模板语言都会输出额外的空格或者回车, JSP也如此,freemaker还专门有一删除多余空格的函数, 在beetl中,是不需要此功能的
上图来源于南磊翻译的中文版Freemarker,说是以上代码会有过多的换行(br),必须使用删除多余空行函数才能使模板输出达到我 们想得样子。Beetl没有此额外函数做这事情,因为Beetl自动就能分辨出这些额外空行。
为什么beetl无需担心额外空行呢,其实很简单,beetl碰到只有beetl语句的行,无论是前有空格还是后有回车,都会忽略的,看过beetl 在优化模式下生成的class代码就很容易能看出来。
3.9. 自定义错误处理
groupTemplate. .setErrorHandler(h) 用于设置你自定义的错误处理,譬如,默认情况下,错误处理会显示错误所在行,错误原因,以及模板上下 3行的内容,如果你不希望客户看到你的模板内容,你可以自定义错误处理, 请参考 org.bee.tl.core . DefaultErrorHandler
4. Spring MVC
4.1. 配置 ViewResolver
为了能在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 默认分别是 <% %> ${ }
4.2. 模板中获取参数
在 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"]}
<% for(user in pageData.dataList!){ if(userLP.index == 10) break; %>
<%}%>
"); for(user in userList){ %> ${userLP.index} ${user.name}
<%}%> <% print("map总共"+map.~size+"
"); for(user in map){ %> ${userLP.index} ${user.key} ${user.value.name}
<%}%> 控制判断 <% for(user in userList){ if(user.age==18){ print("it's lijz'"); continue; }else if(user.age==16){ print("It's lucy"); break; }else{ print("return "); return ; } } print("end loop"); %>
elsefor <% for(user in userList){ print(user.name); }elsefor { print("no data"); } %>
if else <% var user = map["001"]; if(user.name=="lijz"){ print(user.name); }else{ return ; } %>
exist <% print(exist("session")+" "); print(exist("user")+" "); %>
debug <% //debug("仅仅在后台打印,前台什么都没有"); %>
print <% print("hello"); println("hello"); printf("hello,%s,your age is %s","lijz",12+""); %>
numberFormat <% var number = 12.7893; %> number=${number,numberFormat='#.##'} or number=${number,'#.##'}
定义变量 <% var number = 1 ; var str = "hello"; var now = date(); var isChecked = true; var nullValue = null; %> number is ${number},str is ${str},time is ${now} ${isChecked} nullValue=${nullValue}.
多个临时变量 <% var number = 1 ,str="hello",now = date(); %> number is ${number},str is ${str},time is ${now}
推荐 ${map["key1"]}
isEmpty <% var str1 = null; %> ${isEmpty(str1)}
size:${lists.~size!}
<% for(user in lists!){ if(userLP.index == 10) break; %> name:${user.userName} ==== index:${userLP.index}
<%}%>