2011年发布Beetl 0.5的时候,新闻是在Iteye上发布的,老资格程序员可能预料Iteye上会发生什么了,我收到的最多的不是鼓励和喝彩,而是吐槽,”又是一个轮子“是里面最大的声音。尽管4年前的版本还只是个雏形,但实际上已经开始有了与众不同创新点。我将在本文介绍一下Beetl的创新点和创新思路,希望有志从事开源开发的人能借鉴
首先,Beetl是一个脚本风格的模板,这顺应了新时代程序员的审美。
freemarker 当初为什么能从模板引擎中脱颖而出,其实与当时XML流行无不关系。程序员习惯了用XML来处理问题,配置也好,流程说明也好,交互接口也好,无处不有XML,像我一样的大多数程序员,自然就接收了freemaker, 当我在确定Beetl的风格的时候,首先就排除了XML,因为2010后,XML已经不流行了,程序员更加务实了。我打开记事本,写了几种风格的模板语法,最后还是确定了类似JS风格
var a = 1;
var b= a+2;
for(item in items){
print(item.name);
}
尽管语法上没有创新,但将脚本风格的语法应用在模板上还是创新的,独此一家。这是因为:
- 视图渲染往往有更复杂的渲染逻辑,freemaker的xml风格,还是velociy指令风格,力不从心。脚本风格顺应了视图渲染更加复杂和难搞这一事实。
- JS 语法渗透了现在各个编程各个方面,程序员习惯了JS语法,也自然习惯了在模板引擎中使用类似js语法风格的模板引擎
其次,Beetl 能自定义定界符和控制符号,独此一家
模板语言是嵌入在模板里的,所有涉及到定界符号和站位符号,其他模板语言定界符号都是固定的,如JSP总是用<%%> ,php 用的是<? ?>,veclociy 是采用# 和回车换号符号。Beetl 则完全能自定义定界符和占位符号,适用于各种应用场景,比如可以定义#和回车作为定界符号
#for(item in items){
<td>${item.name}</td>
#}
也可以定义注释符号作为定界符
<!--: for(item in items){ -->
<td>${item.name}</td>
<!--:}-->
正如Beetl能自定义定界符和站位符号,Beetl也许是唯一一个能用beetl模板生成beetl模板的模板引擎,而其他模板引擎则太费劲才能完成这个任务
第三: Beetl能对语法树做定制,从而改变渲染逻辑的模板引擎,又是独此一家的模板引擎
Beetl在线体验里,有beetl语法体验,可以输入beetl代码,运行,获得期望输出,如果用户输入while(true), 这将对服务器造成伤害,大部分后来跟随Beetl的在线体验模板引擎,比如freemarker,rythmn,tinytemplate 都对此无能为力,或者通过kill thread方式来避免用户乱搞。但Beetl不是,Beetl定制了While循环处理节点,循环超过5次就结束循环。
还有在我主导的另外一个开源Beetl SQL里,也有类似需要,如下SQL模板,
select * from user where 1=1
-- if(user.name!=null)
and name = ${user.name}
--}
(注,--是定界符,伪装成注释,这样容易在sql客户端调试)当执行模板的时候,${user.name} 并不是需要输出其属性值,而是需要输出?,并记录其值以便随后操作
select * from user where 1=1
and name = ?
只需要定制Beetl模板引擎,修改
PlaceholderST
节点,改成如下代码即可
public final void execute(Context ctx)
{
Object value = expression.evaluate(ctx);
ctx.byteWriter.writeString("?");
List list = (List)ctx.getGlobal("_paras");
list.add(value);
}
定制模板引擎作为高级特性,已经有数个项目被应用到,这在其他模板引擎里不可想象的
第四:Beetl 支持Ajax标记,完美将模板开发和Ajax开发结合起来。
在Beetl推出Ajax标记之前,前端开发者都认为模板引擎和Ajax水火不容,Beet改变了这个现状,通过推出Ajax标志支持,完美结合了模板引擎渲染和无刷新的Ajax。消除了各自的缺点
<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片段结尾
}
%>
如上代码所示,如果渲染整个页面,如render("user.html").则ajax标记忽略,模板正常渲染,如果后台仅仅渲染render("user.html#userTable"); 则模板引擎会找到userTable 的标记,仅仅渲染这一部分。
正如在我的文章里提到过,单纯用js来实现ajax,会有一些问题,如
- SEO无法优化,搜索引擎无能为力
- 头一次访问页面无法立即看到内容,
- 还有对服务器造成过多并发量等
用了Beetl Ajax标记,能完美解决这些问题,其他模板引擎,无论是老资格Freemaker,还是新出现的tinytemplate,都不具备这个功能。我觉得这些模板引擎应该顺应web技术发展新方向,像Beetl那样推出ajax局部渲染技术
第五,Beetl支持单独模板测试,无需控制层以及其提供的模型
尽管所有模板引擎支持MVC分层开发,但实际上在Beetl之前,谁能正正提供分层呢?所谓分层开发,不但是可以单独开发,而且还需要单独测试而无需其他层(M和V)。也只有Beetl才能做到这真正做到一点,关于如何实现,可以参考官网,或者我的一个开源项目BingoUI,没有用任何后台代码实现的一个UI标签库,一套前端代码同时支持pc和mobile
第六,模板语法里有创新语法,也借鉴了其他语言的语法。
Beetl虽然引用在模板领域,但实际上本质上时个脚本语言,为了模板输出定制的语言,因此他有许多创新语法,着在其他模板引擎里很少有,或者他们都是借鉴Beetl的
- 安全输出,user.wfie.name!"单身",安全输出指不存在或者为null的时候输出,可以是常量或者表达式
- 省略的三元表达式,${success?"checked"},如果条件为true,输出checked,否则,什么都不输出
- for(){ } elsefor {} 不像java等语言,如果没有进入循环,什么事情都不需要做,模板语言则不同,因此提供elsefor,这是为模板引擎定制的
- directive safe_output_open 指令,比如打开安全输出,这样,后面代码都不需要输入安全输出符号!
- 模板变量,允许模板输出到一个变量里,方便以后使用,如在继承布局里经常使用模板变量
- ajax标记,如前说叙
- select- case 语法,类似go或者swift里的switch-case
- 虚拟属性语法 如 list.~size.
除此之外,还有很多实用功能,,比如
- 结合本地调用安全管理器,直接调用java代码
- html 标签(定制后能支持父子标签,貌似只有jsp tag能做到)
- 绑定标量的html标签,如<#cms:content id = "" var ="item">...</#cms:content>
- 最完善的错误提示,甚至因为中文标点导致语法错的提示都有
总结
Beetl是个功能强大模板引擎,是除了jsp以外,唯一同时兼顾了脚本和标签的模板语言,本文仅仅列出了Beetl创新部分,回击那些轮子党的轮子言论(尽管轮子党人数庞大,但他们只有一个言论,这很搞笑),让有志从事开源的开发者们借鉴我的开源思路。 同时本文也展示了模板引擎的生命力仍然可以持续和古老的模板引擎技术仍然发扬光大,顺应时代潮流和现代程序审美观。