文档章节

悠然乱弹:“最好的模板引擎”Beetl 剖析及与Tiny模板引擎对比

悠悠然然
 悠悠然然
发布于 2015/08/01 10:46
字数 5566
阅读 4255
收藏 66

Beetl的环境搭建

输入命令

git clone https://git.oschina.net/xiandafu/beetl2.0.git
不一会儿,输出了下面的内容


Cloning into 'beetl2.0'...
remote: Counting objects: 5807, done.
remote: Compressing objects: 100% (2145/2145), done.
remote: Total 5807 (delta 3050), reused 5383 (delta 2733)
Receiving objects: 100% (5807/5807), 14.60 MiB | 684.00 KiB/s, done.
Resolving deltas: 100% (3050/3050), done.
Checking connectivity... done.
嗯嗯,好的开头是成功的一半,不错,代码取下来了。
cd beetl2.0
mvn install
输出结果:
[WARNING] 
[WARNING] Some problems were encountered while building the effective settings
[WARNING] 'servers.server.id' must be unique but found duplicate server with id tiny-nexus-releases @ /Users/luoguo/Develop/apache-maven-3.1.0/conf/settings.xml
[WARNING] 
[INFO] Scanning for projects...
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]   
[ERROR]   The project org.beetl:beetl-core:2.2.4-SNAPSHOT (/Users/luoguo/git/beetl2.0/beetl-core/pom.xml) has 1 error
[ERROR]     Non-resolvable parent POM: Could not find artifact org.beetl:beetl-parent:pom:2.2.4-SNAPSHOT and 'parent.relativePath' points at wrong local POM @ line 4, column 10 -> [Help 2]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException
[ERROR] [Help 2] http://cwiki.apache.org/confluence/display/MAVEN/UnresolvableModelException
咦,这是什么鬼?

猜想是由于我用的是maven 3.1.x导致,于是升级到maven 3.3.3,执行 mvn install,可以看到开始下载相关的资源文件了,OK,起步还是不错的,这里需要耐心等待一段时间。

咦,停止了,看到一堆错误,再看看是什么问题?

[ERROR] /Users/luoguo/git/beetl2.0/beetl-core/src/main/java/org/beetl/ext/jodd/BeetlActionResult.java:[13,8] org.beetl.ext.jodd.BeetlActionResult不是抽象的, 并且未覆盖jodd.madvoc.result.ActionResult中的抽象方法getResultType()
[ERROR] /Users/luoguo/git/beetl2.0/beetl-core/src/main/java/org/beetl/ext/jodd/BeetlActionResult.java:[60,9] 方法不会覆盖或实现超类型的方法
[INFO] 2 errors 
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] beetl-core ......................................... FAILURE [ 44.926 s]
[INFO] beetl-parent ....................................... SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 45.061 s
[INFO] Finished at: 2015-07-28T14:08:38+08:00
[INFO] Final Memory: 18M/262M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project beetl-core: Compilation failure: Compilation failure:
[ERROR] /Users/luoguo/git/beetl2.0/beetl-core/src/main/java/org/beetl/ext/jodd/BeetlActionResult.java:[13,8] org.beetl.ext.jodd.BeetlActionResult不是抽象的, 并且未覆盖jodd.madvoc.result.ActionResult中的抽象方法getResultType()
[ERROR] /Users/luoguo/git/beetl2.0/beetl-core/src/main/java/org/beetl/ext/jodd/BeetlActionResult.java:[60,9] 方法不会覆盖或实现超类型的方法
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
看起来是Beetl继承了jodd的类,但是有些方法没有实现,

没有办法,只要增加fae指令再来执行:

mvn clean install -fae
结果还是原样的错误,至此已经无法进行。

根据文件名分析,这个东东可能是对jodd的一个扩展,理论上可以删除之,于是删除了类BeetlActionResult,然后重新执行mvn install

这次出来的结果是:

[INFO] Reactor Summary:
[INFO] 
[INFO] beetl-core ......................................... SUCCESS [03:52 min]
[INFO] beetl-parent ....................................... SUCCESS [  0.008 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 03:52 min
[INFO] Finished at: 2015-07-28T14:26:09+08:00
[INFO] Final Memory: 25M/309M
[INFO] ------------------------------------------------------------------------
从环境搭建的过程来看,只要是maven 3.3.3,搭建还算顺利,美中不足是有一个Jodd的扩展 BeetlActionResult类有问题。通过直接删除,编译是通过了,有多大的影响,暂时还不清楚。

Beetl工程结构静态分析

从这里看,整体来说还可以,把一些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统计的数据稍有区别,但是基本上差别不大。

从上面的数据可以看出:

项目

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模板引擎则完全遵守规范。
statement
  
    :   block   #blockSt
    |   textStatment    #textOutputSt
    |   constantsTextStatment #staticOutputSt
    |   COMMENT_TAG commentTypeTag  #commentTagSt
    |   If parExpression statement (Else statement)? #ifSt
    |   For LEFT_PAR forControl RIGHT_PAR statement  ( Elsefor statement)?  #forSt
    |   While parExpression statement   #whileSt
    |   Switch parExpression switchBlock    #siwchSt
    |   Select g_switchStatment #selectSt
    |   Try  block (Catch  LEFT_PAR Identifier? RIGHT_PAR  block   )?    #trySt
    |   Return expression? END  #returnSt
    |   Break END   #breakSt
    |   Continue END    #continueSt
    |   Var varDeclareList END  #varSt
    |   Directive  directiveExp #directiveSt 
    |   assignMent END  #assignSt
    |   functionTagCall #functionTagSt 
    |   statementExpression END   #statmentExpSt 
    |   Ajax Identifier COLON block   #ajaxSt 
    |   END   #end
上面是Beetl支持的语法。

tiny模板引擎支持的语法有:

directive   :   set_directive
            |   if_directive
            |   while_directive
            |   for_directive
            |   break_directive
            |   import_directive
            |   continue_directive
            |   stop_directive
            |   include_directive
            |   macro_directive
            |   layout_directive
            |   layout_impl_directive
            |   call_block_directive
            |   call_directive
            |   endofline_directive
            |   blank_directive
            |   tabs_directive
            |   indent_directive
            |   dent_directive
            |   call_macro_directive
            |   call_macro_block_directive
            |   bodycontent_directive
            |   invalid_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没有提供标签的扩展功能,却提供了强大的宏定义功能
简单宏定义 

#macro cmsContent(id)
当前定义了一个内容:${id}
#end
调用方式:
#cmsContent("abc")
带内容宏定义
前置信息
#macro contentFrame()
前置信息
#bodyContent
后置信息
#end
调用方式:
#@contentFrame()
这里是一些信息
#end

运行结果:

后置信息
这里是一些信息
调用方式:
由于Tiny采用的是全部在模板语言中实现的方式,因此定义和使用文本内容更方便,同时在定义和使用时的嵌套支持能力会使得DRY原则得以全面实施,可以整个页面没有重复内容的出现。
布局支持 content.html内容如下:
<%
 //content.html内容如下:
 layout("/inc/layout.html"){%>
 this is 正文
 ..........
 <%%}%>
layout.html 是布局文件
<%
 <%include("/inc/header.html"){} %>
 this is content:${layoutContent}
 this is footer:

 <%%}%>
运行结果:
运行content.html模板文件后,,正文文件的内容将被替换到layoutContent的地方,变成如下内容
this is header
this is content:this is 正文
............
this is footer:

Tiny的做法是:

首先新建content.layout文件

this is header
this is content:#pageContent
this is footer
再新建content.page文件
this is 正文
然后访问content.page,运行结果就是:
this is header
this is content:this is 正文
this is footer
实际上Tiny模板引擎还支持默认布局,多重布局各种花样玩样,由于采用了COC的方式,所以不需要在模板语言中显式引入布局,而是通过目录结构的方式来确定布局渲染方式。在进行重构的时候更也加方便,比如:同样一个文件,放在不同的目录结构中,由于渲染的布局不同,就会出现完全不一样的效果,这在进行重构的时候也更加方便。

Tiny在.layout中还支持指令#layout,如下:

#layout(aaaInfo)
this is aaaInfo
#end

#layout(bbbInfo)
this is bbbInfo
#end
上面就定义了两个布局占位,一个叫aaaInfo,一个叫bbbInfo,

在具体的页面文件中,可以用:

#@layout(aaaInfo)
this is new aaaInfo
#end

#@layout(aaaInfo)
this is new aaaInfo
#end
来覆盖默认的定义,转而显示新的内容,如果不覆盖的话,就显示默认的信息,这里通过引入Java的OverRide的机制,提供了更灵活多变的布局能力。
宏引入

由于Tiny支持把公用的宏用独立的文件来进行存放,相当于Library,但是由于不同的人定义的库有可能有宏名冲突。因此Tiny引入了#import指令来优先使用先import进来的库中的宏,如下:

#import("/a/b/liba.component")
#import("/a/b/libb.component")
如果出现同名的宏,那么liba中的会被执行
安全调用 Beetl采用的是安全表达式的方式来处理安全谳用 Tiny的在调用属性或成员函数时,可以显式用“?.”来表示安全属性调用,而用“.”来表示非安全属性调用,这样写模板时需要明确使用哪个,这样可以及时发现应用中的问题。
错误提示
<%
var a = 1;
var b = a/0;
%>
错误提示如下:
>>DIV_ZERO_ERROR:0 位于3行 资源:/org/beetl/sample/s0125/error1.txt
1|<%
2|var a = 1;
3|var b = a/0;
4|%>
beetl只给出了具体的位置在哪一行,以及整个模板(或者比较近位置的模板)内容。
#set(a=1,b=1/0)
错误提示如下:
路径:/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模板引擎的插件功能

  1. 大纲支持:支持在大纲当中显示一些关键内容,并可以快速定位
  2. 语法高亮:支持在编辑器中,根据语法进行着色,使得代码更容易阅读和排错
  3. 错误提示:如果模板语言存在错误,则可以在工程导航、错误视图及编辑窗口进行错误提示
  4. 代码折叠:支持对代码块进行代码折叠,方便查阅
  5. 语法提示:支持Tiny模板引擎语法提示及Html语法提示方便快速录入
  6. 快速定位:支持Tiny模板中开始语句与结束语句间快速切换
  7. 变量快速提示:点鼠标点击某变量时,会高亮显示文件中的所有同名变量
  8. 宏定义对应位置显示:在tiny块处理的标签头部按ctrl时,会高亮显示与其对应的#end,反之亦然
  9. 格式化:可以按快捷键ctrl+shift+F进行格式化了
  10. 注释处理:可以按快捷键ctrl+/来进行快速设置单行注释或取消单行注释,可以按ctrl+shift+/来进行快速设置块注释或取消块注释

由于篇幅太长,因此这里不贴完整内容,详细请看链接:http://my.oschina.net/tinyframework/blog/365370

OK,工具上完全不在一个等级上。

代码质量对比

代码质量这个本身没有唯一标准,这里贴一下类似的功能的代码对比,不做评论:

for语句实现 

Beetl版

public final class ForStatement extends Statement implements IGoto
{
	public Expression idNode;
	public Expression exp;
	public Statement forPart;
	public Statement elseforPart;
	public boolean hasGoto = false;
	public short itType = 0;
	public boolean hasSafe;

	/**
	 * for(idNode in exp) {forPath}elsefor{elseforPart}
	 * @param idNode
	 * @param exp
	 * @param forPart
	 * @param elseforPart
	 * @param token
	 */
	public ForStatement(VarDefineNode idNode, Expression exp, boolean hasSafe, Statement forPart,
			Statement elseforPart, GrammarToken token)
	{
		super(token);
		this.idNode = idNode;
		this.exp = exp;
		this.hasSafe = hasSafe;
		this.elseforPart = elseforPart;
		this.forPart = forPart;

	}
	public final void execute(Context ctx)
	{
		// idNode 是其后设置的
		int varIndex = ((IVarIndex) idNode).getVarIndex();
		Object collection = exp.evaluate(ctx);
		IteratorStatus it = null;
		if (collection == null)
		{
			if (!this.hasSafe)
			{
				BeetlException ex = new BeetlException(BeetlException.NULL);
				ex.pushToken(exp.token);
				throw ex;
			}
			else
			{
				it = new IteratorStatus(Collections.EMPTY_LIST);
			}
		}
		else
		{
			it = IteratorStatus.getIteratorStatusByType(collection, itType);
			if (it == null)
			{
				BeetlParserException ex = new BeetlParserException(BeetlParserException.COLLECTION_EXPECTED_ERROR);
				ex.pushToken(exp.token);
				throw ex;
			}
		}
		ctx.vars[varIndex + 1] = it;
		// loop_index
		//		ctx.vars[varIndex+2] = 0;
		//		ctx.vars[varIndex+3] = it.getSize();
		//		
		if (this.hasGoto)
		{

			while (it.hasNext())
			{
				ctx.vars[varIndex] = it.next();
				forPart.execute(ctx);
				switch (ctx.gotoFlag)
				{
					case IGoto.NORMAL:
						break;
					case IGoto.CONTINUE:
						ctx.gotoFlag = IGoto.NORMAL;
						continue;
					case IGoto.RETURN:
						return;
					case IGoto.BREAK:
						ctx.gotoFlag = IGoto.NORMAL;
						return;
				}
			}
			if (!it.hasData())
			{
				if (elseforPart != null)
					elseforPart.execute(ctx);
			}
			return;
		}
		else
		{
			while (it.hasNext())
			{
				ctx.vars[varIndex] = it.next();
				forPart.execute(ctx);

			}
			if (!it.hasData())
			{
				if (elseforPart != null)
					elseforPart.execute(ctx);
			}
		}
	}
	@Override
	public final boolean hasGoto()
	{
		// TODO Auto-generated method stub
		return hasGoto;
	}
	@Override
	public final void setGoto(boolean occour)
	{
		this.hasGoto = occour;
	}
	@Override
	public void infer(InferContext inferCtx)
	{
		exp.infer(inferCtx);
		if (exp.getType().types != null)
		{
			if (Map.class.isAssignableFrom(exp.getType().cls))
			{
				idNode.type = Type.mapEntryType;
			}
			else
			{
				//list or array
				idNode.type = exp.getType().types[0];
			}
		}
		else
		{
			idNode.type = Type.ObjectType;
		}
		int index = ((IVarIndex) idNode).getVarIndex();
		inferCtx.types[index] = idNode.type;
		inferCtx.types[index + 1] = new Type(IteratorStatus.class, idNode.type.cls);
		forPart.infer(inferCtx);
		if (elseforPart != null)
		{
			elseforPart.infer(inferCtx);
		}
	}
}

Tiny版

public class ForProcessor implements ContextProcessor<TinyTemplateParser.For_directiveContext> {
    public Class<TinyTemplateParser.For_directiveContext> getType() {
        return TinyTemplateParser.For_directiveContext.class;
    }
    public boolean processChildren() {
        return false;
    }
    public Object process(TemplateInterpreter interpreter, TemplateFromContext templateFromContext, TinyTemplateParser.For_directiveContext parseTree, TemplateContext pageContext, TemplateContext context, TemplateEngineDefault engine, Writer writer, String fileName) throws Exception {
        String name = parseTree.for_expression().IDENTIFIER().getText();
        Object values = interpreter.interpretTree(engine, templateFromContext, parseTree.for_expression().expression(),pageContext, context, writer,fileName);
        ForIterator forIterator = new ForIterator(values);
        context.put(name + "For", forIterator);
        boolean hasItem = false;
        while (forIterator.hasNext()) {
            TemplateContext forContext=new TemplateContextDefault();
            forContext.setParent(context);
            hasItem = true;
            Object value = forIterator.next();
            forContext.put(name, value);
            try {
                interpreter.interpretTree(engine, templateFromContext, parseTree.block(),pageContext, forContext, writer,fileName );
            } catch (ForBreakException be) {
                break;
            } catch (ForContinueException ce) {
                continue;
            }
        }
        if (!hasItem) {
            TinyTemplateParser.Else_directiveContext elseDirectiveContext = parseTree.else_directive();
            if (elseDirectiveContext != null) {
                interpreter.interpretTree(engine, templateFromContext, elseDirectiveContext.block(), pageContext,context, writer,fileName);
            }
        }
        return null;
    }
}

解释引擎核心处理代码

Beetl版

beetl版源代码,由于太长,所以就不贴内容了,详细请点击查看源码

Tiny版

public class TemplateInterpreter {
    TerminalNodeProcessor[] terminalNodeProcessors = new TerminalNodeProcessor[200];
    Map<Class<ParserRuleContext>, ContextProcessor> contextProcessorMap = new HashMap<Class<ParserRuleContext>, ContextProcessor>();
    OtherTerminalNodeProcessor otherNodeProcessor = new OtherTerminalNodeProcessor();


    public void addTerminalNodeProcessor(TerminalNodeProcessor processor) {
        terminalNodeProcessors[processor.getType()] = processor;
    }

    public void addContextProcessor(ContextProcessor contextProcessor) {
        contextProcessorMap.put(contextProcessor.getType(), contextProcessor);
    }

    public TinyTemplateParser.TemplateContext parserTemplateTree(String sourceName, String templateString) {
        char[] source = templateString.toCharArray();
        ANTLRInputStream is = new ANTLRInputStream(source, source.length);
        // set source file name, it will be displayed in error report.
        is.name = sourceName;
        TinyTemplateParser parser = new TinyTemplateParser(new CommonTokenStream(new TinyTemplateLexer(is)));
        return parser.template();
    }

    public void interpret(TemplateEngineDefault engine, TemplateFromContext templateFromContext, String templateString, String sourceName, TemplateContext pageContext, TemplateContext context, Writer writer, String fileName) throws Exception {
        interpret(engine, templateFromContext, parserTemplateTree(sourceName, templateString), pageContext, context, writer,fileName );
        writer.flush();
    }

    public void interpret(TemplateEngineDefault engine, TemplateFromContext templateFromContext, TinyTemplateParser.TemplateContext templateParseTree, TemplateContext pageContext, TemplateContext context, Writer writer, String fileName) throws Exception {
        for (int i = 0; i < templateParseTree.getChildCount(); i++) {
            interpretTree(engine, templateFromContext, templateParseTree.getChild(i), pageContext, context, writer,fileName );
        }
    }

    public Object interpretTree(TemplateEngineDefault engine, TemplateFromContext templateFromContext, ParseTree tree, TemplateContext pageContext, TemplateContext context, Writer writer, String fileName) throws Exception {
        Object returnValue = null;
        if (tree instanceof TerminalNode) {
            TerminalNode terminalNode = (TerminalNode) tree;
            TerminalNodeProcessor processor = terminalNodeProcessors[terminalNode.getSymbol().getType()];
            if (processor != null) {
                returnValue = processor.process(terminalNode, context, writer);
            } else {
                returnValue = otherNodeProcessor.process(terminalNode, context, writer);
            }
        } else if (tree instanceof ParserRuleContext) {
            try {
                ContextProcessor processor = contextProcessorMap.get(tree.getClass());
                if (processor != null) {
                    returnValue = processor.process(this, templateFromContext, (ParserRuleContext) tree, pageContext, context, engine, writer,fileName);
                }
                if (processor == null || processor != null && processor.processChildren()) {
                    for (int i = 0; i < tree.getChildCount(); i++) {
                        Object value = interpretTree(engine, templateFromContext, tree.getChild(i), pageContext, context, writer,fileName );
                        if (value != null) {
                            returnValue = value;
                        }
                    }
                }
            } catch (StopException se) {
                throw se;
            } catch (TemplateException te) {
                if (te.getContext() == null) {
                    te.setContext((ParserRuleContext) tree,fileName);
                }
                throw te;
            } catch (Exception e) {
                throw new TemplateException(e, (ParserRuleContext) tree,fileName);
            }
        } else {
            for (int i = 0; i < tree.getChildCount(); i++) {
                Object value = interpretTree(engine, templateFromContext, tree.getChild(i), pageContext, context, writer,fileName );
                if (returnValue == null && value != null) {
                    returnValue = value;
                }
            }
        }
        return returnValue;
    }

    public static void write(Writer writer, Object object) throws IOException {
        if (object != null) {
            writer.write(object.toString());
        }
    }
}

嗯嗯,不到100行的规模

当然整个通读下来,就会慢慢发现为什么Tiny的代码行数这么少功能却又多的原因之所在了。

总结

Beetl算得上是较好的模板语言框架和不错的开源项目,但是距离“最好的”三个字还是有一定差距的,作为@闲.大赋 的粉丝,偶会持续支持他,也希望他能再积再累,真正当得起“最好的”三个字。

补充说明

beetl里面有4014行由antlr生成的代码,实际统计中,由于Beetl的目录结构没有按标准化的来,导致统计中包含了这部分代码,因此实际上,应该是在16000+,因此规模是Tiny模板引擎的3倍左右,特此纠正。

© 著作权归作者所有

悠悠然然

悠悠然然

粉丝 2471
博文 185
码字总数 363071
作品 14
杭州
架构师
私信 提问
加载中

评论(44)

悠悠然然
悠悠然然 博主

引用来自“灰度乌鸦”的评论

@悠悠然然: 请教一下这篇博客 这个 http://static.oschina.net/uploads/space/2015/0728/145910_iQPb_1245989.png 图中的代码统计用的是什么软件?

回复@灰度乌鸦 : idea statics插件
灰度乌鸦
灰度乌鸦
@悠悠然然: 请教一下这篇博客 这个 http://static.oschina.net/uploads/space/2015/0728/145910_iQPb_1245989.png 图中的代码统计用的是什么软件?
悠悠然然
悠悠然然 博主

引用来自“中国首席鉴黄师”的评论

文中暗含的意思就是说自己的东西比别人的好,却在结尾说自己是别人的粉丝,虽然看上去好像是表示友好给台阶,实际上更加暴露出自己内心莫名其妙的优越感,更暴露出对此次对比信心不足。并且可以看出博主已经逐渐丧失了程序员的真诚,取而代之的是多出了管理者的套路。

可以看到 beetl 在工程上确实有些随意了,这也是文中一直在强调的,有理有据。作为具有不同风格语法的模板引擎,能对比的就是性能而已,并不需要就语言风格本身做过多的评价,萝卜青菜的事情而已。最最幼稚的是比代码量,让我感觉是不是实在没什么好比的了...
对于第一段不作评价。

代码量其实也是非常重要的一个参量。
对于不同的东西来说,代码量没有可比性。但是同一个东西,代码量差许多,最起码说明开发、维护、支持工作量上的差异,有些时候,也是性能上的差异----或者更快,或者更慢。
实际上性能上的差异,对于模板语言这个领域来说,并没有多重要,毕竟主要的时间都是化在业务数据的获取上的,虽然评测的模板语言都比Velocity&FreeMarker快许多,但是实际上V&F在日常使用中,都足够足够的了。所以功能特性及易用性方面的差异更重要。
中国首席鉴黄师
中国首席鉴黄师
文中暗含的意思就是说自己的东西比别人的好,却在结尾说自己是别人的粉丝,虽然看上去好像是表示友好给台阶,实际上更加暴露出自己内心莫名其妙的优越感,更暴露出对此次对比信心不足。并且可以看出博主已经逐渐丧失了程序员的真诚,取而代之的是多出了管理者的套路。

可以看到 beetl 在工程上确实有些随意了,这也是文中一直在强调的,有理有据。作为具有不同风格语法的模板引擎,能对比的就是性能而已,并不需要就语言风格本身做过多的评价,萝卜青菜的事情而已。最最幼稚的是比代码量,让我感觉是不是实在没什么好比的了...
悠悠然然
悠悠然然 博主

引用来自“爱我老婆”的评论

还是beetl得语法和入门简单,喜欢这种类似js的语法,已经在项目使用

嗯嗯,喜欢就好。
爱我老婆
爱我老婆
还是beetl得语法和入门简单,喜欢这种类似js的语法,已经在项目使用
悠悠然然
悠悠然然 博主

引用来自“ylmotol7”的评论

大概看了下tiny框架,我觉得这种框架就是我需要或者想构建的(半年前已经构建好了我自己的框架),感觉有点类似积木的感觉,相信搭建项目非常快。

欢迎多多参与我们的讨论。
y
ylmotol7
大概看了下tiny框架,我觉得这种框架就是我需要或者想构建的(半年前已经构建好了我自己的框架),感觉有点类似积木的感觉,相信搭建项目非常快。
悠悠然然
悠悠然然 博主

引用来自“liujiduo”的评论

总的来说楼主的评比还是比较客观的,尤其是代码对比部分,很明显tiny的代码更加简洁干练,逻辑也更加清晰。tiny模板和beetl我都没用过,但都了解过,以前用过freemarker,也比较喜欢那种指令定义代码块而不是类似jsp的<%{%>方式,所以tiny的语法反而更容易被我接受,我也考虑过用tiny模板,就是不清楚好不好和springmvc集成啥的。以上均是个人观点,不过两位都是大牛,能写出来这样的开源项目也让我羡慕不已,希望你们都能很好地发展下去,继续为开源世界添砖加瓦,你们都是我们学习的榜样13

引用来自“闲.大赋”的评论

beetl可以自定义 定界符号,所有 # for() 也支持的 http://my.oschina.net/xiandafu/blog/486720 模板引擎Beet的6大创新点

引用来自“Jieven”的评论

我也正想说这事,最新核心的 性能比拼图也没看见,代码质量好,也不能说明问题,Freemaker那么渣 也能火
${"abc"}HelloWorld#for(i:[1..20])${i}#end HelloWorld 这一段模板语言,渲染200万次 2014顶级15寸MAC 执行时间为15929 DELL6420 Win7,执行时间为18347 想去弄TEB来着,但是太复杂麻烦了。
悠悠然然
悠悠然然 博主

引用来自“远志”的评论

比较关心两种模板最新版之间的性能对比。

另外两种都应该支持页面的静态化处理吧。
${"abc"}HelloWorld#for(i:[1..20])${i}#end HelloWorld 这一段模板语言,渲染200万次 2014顶级15寸MAC 执行时间为15929 DELL6420 Win7,执行时间为18347 想去弄TEB来着,但是太复杂麻烦了。
剖析 “‘最好的模板引擎’Beetl剖析及与Tiny模板引擎对比”

http://blog.csdn.net/cndes/art ... 88771 这有一篇文章,说是剖析beetl模板引擎,并与同为国内的tiny模板引擎做对比(以下简称“剖析beetl”),其剖析过程公正,但结论却不正确(文中暗示的...

闲大赋
2016/07/14
755
9
悠然乱弹:论“轮子党”的邪恶面

什么叫轮子党呢?这里给出一个泛泛的定义,轮子党,就是看到开源作者做的作品之后,干净利索的在后面附着两字:轮子。当然现在也有一些新的变体,比如:发一条“然并卵”。但是不管现在怎么变...

悠悠然然
2015/08/07
2.8K
30
TinyTemplate模板引擎火热出炉,正式开源了~~~

涉水模板引擎领域,纯属不小心。 在此对以下人员表示强烈感谢与致敬: @sub jetbrick作者 @sept @webit webit作者 @罗格林 rythm作者 @闲.大赋 beetl作者 以及许许多多虽然没有列出来,但是在...

悠悠然然
2014/06/18
3.2K
29
TinyTemplate(Velocity Plus版)即将火热推出~~~

本来是没有自己写一个模板引擎的计划的,因为按我的理解,一直认识这种“语言”级的引擎,难度是非常大的。总感觉自己的水平不够,因此不敢有这个念头。直到大量使用Velocty的时候,碰到vel...

悠悠然然
2014/06/11
640
11
第五章:视图技术之-Beetl模板引擎

1、特性介绍 功能完备:作为主流模板引擎,Beetl具有相当多的功能和其他模板引擎不具备的功能。适用于各种应用场景,从对响应速度有很高要求的大网站到功能繁多的CMS管理系统都适合。Beetl本...

刘祖鹏
2018/05/19
343
0

没有更多内容

加载失败,请刷新页面

加载更多

关于AsyncTask的onPostExcute方法是否会在Activity重建过程中调用的问题

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/XG1057415595/article/details/86774575 假设下面一种情况...

shzwork
今天
6
0
object 类中有哪些方法?

getClass(): 获取运行时类的对象 equals():判断其他对象是否与此对象相等 hashcode():返回该对象的哈希码值 toString():返回该对象的字符串表示 clone(): 创建并返此对象的一个副本 wait...

happywe
今天
6
0
Docker容器实战(七) - 容器中进程视野下的文件系统

前两文中,讲了Linux容器最基础的两种技术 Namespace 作用是“隔离”,它让应用进程只能看到该Namespace内的“世界” Cgroups 作用是“限制”,它给这个“世界”围上了一圈看不见的墙 这么一...

JavaEdge
今天
8
0
文件访问和共享的方法介绍

在上一篇文章中,你了解到文件有三个不同的权限集。拥有该文件的用户有一个集合,拥有该文件的组的成员有一个集合,然后最终一个集合适用于其他所有人。在长列表(ls -l)中这些权限使用符号...

老孟的Linux私房菜
今天
7
0
面试套路题目

作者:抱紧超越小姐姐 链接:https://www.nowcoder.com/discuss/309292?type=3 来源:牛客网 面试时候的潜台词 抱紧超越小姐姐 编辑于 2019-10-15 16:14:56APP内打开赞 3 | 收藏 4 | 回复24 ...

MtrS
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部