查找最好的模板引擎,发现这个搜索词出来的是beetl,于是就仔细学习了Beetl,试图找寻“最好的”三个字表现在哪里?于是搭建环境,阅读代码,与鄙人所做的TinyTemplate进行了粗略的对比,在征得beetl作者@闲.大赋 的同意后,编写了此对比文章。由于时间关系,对Beetl的认知深度还有不足,分析不当之处在所难免,还请广大同学纠正,定当有错误和不当必改。
点滴悟透设计思想,加入框架设计兴趣小组:
Beetl的环境搭建 输入命令
1 | [WARNING] Some problems were encountered while[INFO] Scanning for[ERROR] [ERROR] Non-resolvable parent POM: Could not find[ERROR] [ERROR] For more[INFO] -------------------------------------------------------------[INFO] beetl-core ......................................... FAILURE [ 44.926[ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles:[INFO] beetl-core ......................................... SUCCESS [03:52 min][INFO] ------------------------------------------------------------------------ 从这里看,整体来说还可以,把一些bak文件上传上来,稍嫌不严谨,另外有些jpg文件直接放在根目录也有一点点乱,如果整理一下就更好了。 接下来比较关心core 这里面有几个东东,就有点难理解了,为什么这里放了个jar文件?为什么这里放了个lib目录?为什么这里放了个performance工程?性能评测的代码怎么会放到core工程中?? 上面这个应该就是关键工程了?core应该就是引擎核心代码所在的位置,ext应该是它对各种开源框架方面的扩展或支持。有这些扩展还是非常不错的,方便使用者上手,赞一个。但是把ext和core放在一个工程里还是有点随意了,如果能把ext单独开个工程就更好了。 从上面的目录结构看还是不错的,但是很显然下面的一些类和接口看起来就比较乱了,应该相当有改进的空间。 相对应的,可以看看Tiny模板引擎的目录结构: 就简洁清爽多了。 再来看看beetl模板的代码行数: 可以看到core工程中的java代码是20291行,不算空行,不算注释行。 Tiny模板引擎的代码行数,纯纯的java代码只有4944行,也就是beetl的代码整整是Tiny模板引擎4倍多。 上面是Beetl的sonar检查情况 上面的统计数据是Tiny模板引擎的统计数据: 这里的数据和上面用Statistics统计的数据稍有区别,但是基本上差别不大。 从上面的数据可以看出:
Tiny模板引擎则完全遵守规范。
|
123456789101112131415161718192021222324 | | if_directive | for_directive | import_directive | stop_directive | macro_directive | layout_impl_directive | call_directive | blank_directive | indent_directive | call_macro_directive | bodycontent_directive ; |
- 循环指令两者都支持for和while,都支持break,contine,stop/return等。同时也都支持else,也就是当循环次数为0时,执行一些操作,比如:有数据的时候在循环体内展示数据,没有数据的时候显示else中的默认内容。
- 在条件判断方面Beetl支持了if、switch、select等指令,而tiny模板引擎则是由强大的#if() ... #elseif()... #else...#end指令格式来完成所有的条件判断,两者功能都可以互相覆盖。
项目 | Beetl | Tiny |
定义临时变量 | var number=1 | #set(number=1) |
定义页面变量 | template.binding("number",1) | #!set(number=1) |
属性引用 | ${user.wife.name} | ${user.wife.name} |
算述表达式 | <%var a1 = 12;var b1 = (a1+15)/3-2*a1;var bc = -1-b1;%>${bc} | #set(a1=12,b1 = (a1+15)/3-2*a1,bc = -1-b1)${bc}当然,#set指令也可以一行写一个赋值指令 |
逻辑表达式 | <%var a1 = 12;var b1 = a1==12;var b2 = a1!=12;%>${b1}${b2} | #set(a1 = 12,b1 = a1==12,b2 = a1!=12)${b1}${b2} |
循环语句 | <%print("总共"+userList.~size+"<br>");for(user in userList){ %>${userLP.index} ${user.name} <br><%}%> | 总共${userList.size()}#for(user in userList)${userFor.index} ${user.name}#end |
条件语句 | <%var user = map["001"];if(user.name=="lijz"){ print(user.name);}else{ return ;}%> | #set(user = map."001")#if(user.name=="lijz") ${user.name}#else #return#end |
函数调用 | <%print("hello");println("hello");printf("hello,%s,your age is %s","lijz",12+"");%> | ${format("hello")}${format("hello\n")} ${format("hello,%s,your age is %s","lijz",12)} |
格式化 | <%var now = date();var date = date("2013-1-1","yyyy-MM-dd");%>now=${now,dateFormat='yyyy年MM月dd日'}date=${date,dateFormat='yyyy年MM月dd日'}ornow=${now,'yyyy年MM月dd日'} | tiny模板引擎不允许动态创建对象,但是允许通过自定义函数或SpringBean来获取对象。假设,这里在上下文中在now和date两个变量now=${format(now,'yyyy年MM月dd日 HH:mm:SS')}date=${format(date,'yyyy年MM月dd日')} |
成员方法调用 | <% var list = [5,2,4];%>${ @java.util.Collections.max(list)} | #set( list = [5,2,4])${list.get(1)} |
安全输出 | <%var user1 = null;var user2 = null;var user3 = {"name":"lijz",wife:{'name':'lucy'}};%> ${user1.wife.name!"单身"}${user2.wife.name!}${user3.wife.name!"单身"} | #set(user1 = null,user2 = null,user3 = {"name":"lijz",wife:{'name':'lucy'}}) %> ${user1?.wife?.name?:"单身"}${user2?.wife?.name?:"单身"}${user3?.wife?.name?:"单身"} |
注释 | <% //最大值是12;/*最大值是12*/var max = 12;%> | ##最大值是12; #*最大值是12*# #set( max = 12) |
上面做了两个模板引擎的常规指令的示例和对比,基本上采用Beetl在线示例中的示例然后用Tiny模板引擎的语法来同样实现的功能。
下面来说说一些有意思的高级功能项目 | Beetl | Tiny模板引擎 | |||||||||||||
异常处理 | <%try{ callOtherSystemView()}catch(error){ print("暂时无数据");}%> | Tiny模板引擎的设计者认为如果让模板引擎来处理异常,实际上是有点过度设计的意味,而应该是系统的异常处理框架去处理之。模板只参与展示层的处理,不参与业务逻辑处理。 | |||||||||||||
虚拟属性 |
| ${user.toJson()}Tiny支持为某种类增加一些扩展的成员函数,和Beetl的虚拟属性的意思是相同的,但是在函数调用过程中,使用方式与原生成员函数没有区别。如果扩展的方法是getXxx,那么就可以直接调用object.xxx的方式按属性的方式来进行调用。 | |||||||||||||
函数扩展 | <%var date = date();var len = strutil.len("cbd");println("len="+len);%> | Tiny也提供了函数扩展体系,也完全可以添加类似的函数扩展,调用方式也差不多。#set(date =date(),len=strutil.len("cbd")) | |||||||||||||
标签的支持 | public class CmsContentTag extends GeneralVarTagBinding { public void render(){ Object id= this.getAttributeValue("id");try{ctx.byteWriter.writeString("当前定义了一个窜上:"+id.toString());}catch (IOException e){ e.printStackTrace();}}} | Tiny没有提供标签的扩展功能,却提供了强大的宏定义功能简单宏定义
|
| ||||||||||||
- 大纲支持:支持在大纲当中显示一些关键内容,并可以快速定位
- 语法高亮:支持在编辑器中,根据语法进行着色,使得代码更容易阅读和排错
- 错误提示:如果模板语言存在错误,则可以在工程导航、错误视图及编辑窗口进行错误提示
- 代码折叠:支持对代码块进行代码折叠,方便查阅
- 语法提示:支持Tiny模板引擎语法提示及Html语法提示方便快速录入
- 快速定位:支持Tiny模板中开始语句与结束语句间快速切换
- 变量快速提示:点鼠标点击某变量时,会高亮显示文件中的所有同名变量
- 宏定义对应位置显示:在tiny块处理的标签头部按ctrl时,会高亮显示与其对应的#end,反之亦然
- 格式化:可以按快捷键ctrl+shift+F进行格式化了
- 注释处理:可以按快捷键ctrl+/来进行快速设置单行注释或取消单行注释,可以按ctrl+shift+/来进行快速设置块注释或取消块注释
由于篇幅太长,因此这里不贴完整内容,详细请看链接:
OK,工具上完全不在一个等级上。 代码质量对比代码质量这个本身没有唯一标准,这里贴一下类似的功能的代码对比,不做评论: for语句实现 Beetl版
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147 | Expression idNode;Expression exp;Statement forPart;Statement elseforPart;boolean public public * for(idNode in exp) {forPath}elsefor{elseforPart} * @param exp * @param elseforPart */ForStatement(VarDefineNode idNode, Expression exp, boolean { this.idNode = idNode; this.hasSafe = hasSafe; this.forPart = forPart; public // idNode 是其后设置的 Object collection = exp.evaluate(ctx); if if BeetlException ex = new ex.pushToken(exp.token);ex; else it = new } else it = IteratorStatus.getIteratorStatusByType(collection, itType);(it == null) BeetlParserException ex = new ex.pushToken(exp.token);ex; } // loop_index // ctx.vars[varIndex+3] = it.getSize(); if(it.hasNext()) ctx.vars[varIndex] = it.next(); switch case case continue;IGoto.RETURN: case return; }(!it.hasData()) if } } { (it.hasNext()) ctx.vars[varIndex] = it.next(); if if } } public // TODO Auto-generated method stub } public this.hasGoto = occour; @Overridevoid exp.infer(inferCtx);(exp.getType().types != null) if idNode.type = Type.mapEntryType; else //list or array } else idNode.type = Type.ObjectType; int inferCtx.types[index] = idNode.type;Type(IteratorStatus.class, idNode.type.cls); if elseforPart.infer(inferCtx); } public return }booleanfalse; public Object values = interpreter.interpretTree(engine, templateFromContext, parseTree.for_expression().expression(),pageContext, context, writer,fileName);ForIterator(values);hasItem = false;(forIterator.hasNext()) { TemplateContextDefault(); hasItem = true; forContext.put(name, value);{ } catch } catch } if if } return }查看源码Tiny版
|
嗯嗯,不到100行的规模 当然整个通读下来,就会慢慢发现为什么Tiny的代码行数这么少功能却又多的原因之所在了。 总结Beetl算得上是较好的模板语言框架和不错的开源项目,但是距离“最好的”三个字还是有一定差距的,作为@闲.大赋 的粉丝,偶会持续支持他,也希望他能再积再累,真正当得起“最好的”三个字。补充说明 beetl里面有4014行由antlr生成的代码,实际统计中,由于Beetl的目录结构没有按标准化的来,导致统计中包含了这部分代码,因此实际上,应该是在16000+,因此规模是Tiny模板引擎的3倍左右,特此纠正。
欢迎访问开源技术社区:。本例涉及的代码和框架资料,将会在社区分享。《自己动手写框架》成员QQ群:228977971,一起动手,了解开源框架的奥秘!或点击加入QQ群: