《開源框架那些事兒26》:「最好的模板引擎」Beetl剖析及與Tiny模板引擎對比

查找最好的模板引擎,發現這個搜索詞出來的是beetl,因而就仔細學習了Beetl,試圖找尋「最好的」三個字表如今哪裏?因而搭建環境,閱讀代碼,與鄙人所作的TinyTemplate進行了粗略的對比,在徵得beetl做者@閒.大賦 的贊成後,編寫了此對比文章。因爲時間關係,對Beetl的認知深度還有不足,分析不當之處在所不免,還請廣大同窗糾正,定當有錯誤和不當必改。html

 

點滴悟透設計思想,加入框架設計興趣小組: http://bbs.tinygroup.org/group-113-1.html

 


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 java


這裏面有幾個東東,就有點難理解了,爲何這裏放了個jar文件?爲何這裏放了個lib目錄?爲何這裏放了個performance工程?性能評測的代碼怎麼會放到core工程中?? node


上面這個應該就是關鍵工程了?core應該就是引擎核心代碼所在的位置,ext應該是它對各類開源框架方面的擴展或支持。有這些擴展仍是很是不錯的,方便使用者上手,贊一個。可是把ext和core放在一個工程裏仍是有點隨意了,若是能把ext單獨開個工程就更好了。 git


從上面的目錄結構看仍是不錯的,可是很顯然下面的一些類和接口看起來就比較亂了,應該至關有改進的空間。 相對應的,能夠看看Tiny模板引擎的目錄結構: 程序員


就簡潔清爽多了。
再來看看beetl模板的代碼行數: ajax


能夠看到core工程中的java代碼是20291行,不算空行,不算註釋行。 express


Tiny模板引擎的代碼行數,純純的java代碼只有4944行,也就是beetl的代碼整整是Tiny模板引擎4倍多。 安全


上面是Beetl的sonar檢查狀況 框架


上面的統計數據是Tiny模板引擎的統計數據:
這裏的數據和上面用Statistics統計的數據稍有區別,可是基本上差異不大。
從上面的數據能夠看出:

eclipse

項目 Beetl Tiny模板引擎
代碼行數 23087 4944
文件數 230 171  
重複 3.3% 0.0%
複雜度 2.8/方法 1.9/方法
包耦合指數 31.5% 31.6%
包耦合循環 >35 >18

從代碼規模來講,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規範呢? 

 


Tiny模板引擎則徹底遵照規範。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    :   block   #blockSt    |   constantsTextStatment #staticOutputSt
    |   If parExpression statement (Else statement)? #ifSt    |   While parExpression statement   #whileSt    |   Select g_switchStatment #selectSt    |   Return expression? END  #returnSt    |   Continue END    #continueSt    |   Directive  directiveExp #directiveSt     |   functionTagCall #functionTagSt     |   Ajax Identifier COLON block   #ajaxSt     |   END   #end



上面是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模板引擎都有良好的支持

 



  • 循環指令二者都支持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日'}
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.~genderShowName}
${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沒有提供標籤的擴展功能,卻提供了強大的宏定義功能
簡單宏定義
1
2
3
//content.html內容以下:
this is 正文

..........

<%%}%>


layout.html 是佈局文件
1
2
3
4
5
6
<%include("/inc/header.html"){} %>
this is content {layoutContent}
this is footer:

<%%}%>


運行結果:
運行content.html模板文件後,,正文文件的內容將被替換到layoutContent的地方,變成以下內容
1
2
3
4
this is content:#pageContent
var a = 1;

%>


錯誤提示以下:

1
2
3
4
5
1|<%
3|var b = a/0;

4|%>


beetl只給出了具體的位置在哪一行,以及整個模板(或者比較近位置的模板)內容。
1 #set(a=1,b=1/0)


錯誤提示以下:
1
2
3
4
5
路徑:/a.page
位置[1行11列]-[1行13列]

===================================================================

1/0

===================================================================


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模板引擎的插件功能

 



  • 大綱支持:支持在大綱當中顯示一些關鍵內容,並能夠快速定位
  • 語法高亮:支持在編輯器中,根據語法進行着色,使得代碼更容易閱讀和排錯
  • 錯誤提示:若是模板語言存在錯誤,則能夠在工程導航、錯誤視圖及編輯窗口進行錯誤提示
  • 代碼摺疊:支持對代碼塊進行代碼摺疊,方便查閱
  • 語法提示:支持Tiny模板引擎語法提示及Html語法提示方便快速錄入
  • 快速定位:支持Tiny模板中開始語句與結束語句間快速切換
  • 變量快速提示:點鼠標點擊某變量時,會高亮顯示文件中的全部同名變量
  • 宏定義對應位置顯示:在tiny塊處理的標籤頭部按ctrl時,會高亮顯示與其對應的#end,反之亦然
  • 格式化:能夠按快捷鍵ctrl+shift+F進行格式化了
  • 註釋處理:能夠按快捷鍵ctrl+/來進行快速設置單行註釋或取消單行註釋,能夠按ctrl+shift+/來進行快速設置塊註釋或取消塊註釋

因爲篇幅太長,所以這裏不貼完整內容,詳細請看連接: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版

 

 

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
TerminalNodeProcessor[200];
HashMap<Class<arserRuleContext>, ContextProcessor>();

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

相關文章
相關標籤/搜索