查找最好的模板引擎,發現這個搜索詞出來的是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