查找最好的模板引擎,发现这个搜索词出来的是beetl,因而就仔细学习了Beetl,试图找寻“最好的”三个字表如今哪里?因而搭建环境,阅读代码,与鄙人所作的TinyTemplate进行了粗略的对比,在征得beetl做者@闲.大赋 的赞成后,编写了此对比文章。因为时间关系,对Beetl的认知深度还有不足,分析不当之处在所不免,还请广大同窗纠正,定当有错误和不当必改。html
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] ------------------------------------------------------------------------ ![]()
从代码规模来讲,Tiny完胜,只有Beetl的不到1/4。代码重复率方面Beetl也至关不错了,固然Tiny的更好一点。复杂度Beetl方面也不错,固然 Tiny的要更好一点。包耦合指数方面差很少,可是包耦合循环方面,tiny只有Beetl的一半。 从上面的数据来看,Tiny的方法更小,包依赖的长度更短,更容易维护。 OK,从上面的静态分析来看,Beetl的包结构组织有进步的空间,有必定的代码重复,总体代码质量还不错,可是包耦合度有点高,因此其可维护性较Tiny稍弱。 Beetl语法 到main/antlr中查找Beetl语法定义文件,竟然没有找到,最后终于在下面的位置main/java/org/beetl/core/parser/BeetlParser.g4找到了,为何不能彻底遵循Maven规范呢?
|
上面是Beetl支持的语法。 tiny模板引擎支持的语法有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
| if_directive | for_directive | import_directive | stop_directive | macro_directive | layout_impl_directive | call_directive | blank_directive | indent_directive | call_macro_directive | bodycontent_directive ; |
两者作个对比: 语法体系的差别,Beetl采用的是相似jsp的方式,而Tiny模板引擎采用的是Velocity的方式,两者各有优缺点,所以并没有好坏之分,只是萝卜青菜上的差别。从我本人来讲,是很是讨厌相似于<% ... %>来方式圈定脚本,而更喜欢Velocity的直接用指令嵌入的方式来进行使用,因此我选择了类 Velocity的方式。所以语法体系方面没有什么比如较的。
对于常规指令Beetl和Tiny模板引擎都有良好的支持
项目 | 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日'} or now=${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没有提供标签的扩展功能,却提供了强大的宏定义功能 简单宏定义
layout.html 是布局文件
运行结果: 运行content.html模板文件后,,正文文件的内容将被替换到layoutContent的地方,变成以下内容
错误提示以下:
beetl只给出了具体的位置在哪一行,以及整个模板(或者比较近位置的模板)内容。 |
错误提示以下:
Tiny则明确给出了精确的坐标,x1,y1-x2,y2,同时还给出了具体出问题的内容,相对来讲程序员查找问题更加迅捷。 |
||||||||||||
工具的支持 beetl的插件功能 Beetl插件如约而来!
安装说明:
本插件是beetl模板语言插件,请放到dropins目录下重启便可。若是之前安装过,须要删除之前保本
若是文件以.btl结尾,则自动以插件方式打开,不然,能够经过右键此文件,选择open-with,并选择beetl editor,不建议使用btl结尾,请尽可能使用原有编辑器,参考使用说明4快捷使用beetl editor
使用说明:
1 工程属性里有个beetl属性,能够指定定界符号等,默认是<%%> ${}。也能够指定模板根目录(可选,没必要手工填写,在模板单击定位里会提示你选择)2 ctrl-2 定位到下一个beetl 块3 ctrl-3 定位到上一个beetl块4 ctrl-4 将普通文件以beetl editor方式打开,并保持同步编辑 5 ctrl-5 静态文本所有折叠和打开静态文本折叠6 能够ctrl+单击字符串定位到字符串对应的模板文件,第一次使用的时候,须要选择模板根目录,随后,也能够在project属性的beetl配置里配置模板根目录7 alt-/ 进行上下文提示。也能够键入此快速输入定界符号和占位符号8 alt-shift-p 从{ 快速移动到 匹配的},或者反之亦然。若是只单击{ 则会框选住匹配的} 而光标不移动9 选中任何id,都能全文框选住一样的id。10 ctrl-/ 单行注释,或者取消注释11 一般eclipse具备的快捷操做方式,beetl仍然予以保留不变 12 具有必定的错误提示,目前只提示第一个发现的错误。Tiny模板引擎的插件功能
因为篇幅太长,所以这里不贴完整内容,详细请看连接:http://my.oschina.net/tinyframework/blog/365370
OK,工具上彻底不在一个等级上。
代码质量对比
代码质量这个自己没有惟一标准,这里贴一下相似的功能的代码对比,不作评论:
for语句实现 Beetl版
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 135 136 137 138 139 140 141 142 143 144 145 146 147 |
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; @Override void 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版
OtherTerminalNodeProcessor(); void } void } TinyTemplateParser.TemplateContext parserTemplateTree(String sourceName, String templateString) { ANTLRInputStream is = new // set source file name, it will be displayed in error report. TinyTemplateParser parser = new return } void writer.flush(); void(int interpretTree(engine, templateFromContext, templateParseTree.getChild(i), pageContext, context, writer,fileName ); } Object interpretTree(TemplateEngineDefault engine, TemplateFromContext templateFromContext, ParseTree tree, TemplateContext pageContext, TemplateContext context, Writer writer, String fileName) throws if TerminalNodeProcessor processor = terminalNodeProcessors[terminalNode.getSymbol().getType()]; (processor != null) { } else } if{ if } (processor == null for Object value = interpretTree(engine, templateFromContext, tree.getChild(i), pageContext, context, writer,fileName ); (value != null) { } } (StopException se) { se; (TemplateException te) { (te.getContext() == null) { } te; (Exception e) { new } { (int Object value = interpretTree(engine, templateFromContext, tree.getChild(i), pageContext, context, writer,fileName ); (returnValue == null returnValue = value; } return } static(object != null) { } } |
嗯嗯,不到100行的规模
固然整个通读下来,就会慢慢发现为何Tiny的代码行数这么少功能却又多的缘由之所在了。
总结
Beetl算得上是较好的模板语言框架和不错的开源项目,可是距离“最好的”三个字仍是有必定差距的,做为@闲.大赋 的粉丝,偶会持续支持他,也但愿他能再积再累,真正当得起“最好的”三个字。
补充说明
beetl里面有4014行由antlr生成的代码,实际统计中,因为Beetl的目录结构没有按标准化的来,致使统计中包含了这部分代码,所以实际上,应该是在16000+,所以规模是Tiny模板引擎的3倍左右,特此纠正。
欢迎访问开源技术社区:http://bbs.tinygroup.org。本例涉及的代码和框架资料,将会在社区分享。《本身动手写框架》成员QQ群:228977971,一块儿动手,了解开源框架的奥秘!或点击加入QQ群:http://jq.qq.com/?_wv=1027&k=d0myfX