打造一个自己的模板引擎(二)
上一篇我们使用递归实现了一个模板渲染器,在模板结构复杂,嵌套比较多的时候,递归很容易把jvm的栈搞的很大,甚至崩溃。
因此接下来我们想办法避免使用递归。
静态语言在执行的时候,比如:if(my.say() == 'hello'){out.print("hello")}
用xml来表示:
<c:if test="${my.say() == 'hello'}">hello</c:if>
静态语言怎么执行呢?执行到if的时候,发现条件是个语句,并且还有个函数, 那么先把函数代码调入内存,如果有参数,把参数压入栈, 把程序指针指向该函数,
然后执行,执行完之后把结果压入栈,返回调用处,调用的地方从栈顶取出结果,然后继续执行。
我们可以考虑用类似的方式, 当遇到一个节点的时候,先调用doStartTag, 然后根据返回值决定是否要执行子节点,如果需要执行子节点,那么就把子节点压入栈,然后返回即可.
子节点执行完之后,需要调用doEndTag, 因此再把子节点压入栈之前先把当前节点压入栈,调用完doEndTag之后如果需要重复执行子节点,那么就再把自己压入栈,然后把子节点压入栈。
这样就可以不使用递归了,把递归处理变成了循环处理。而且很容易控制整个循环是否结束. 因为jstl的标签里面有个SKIP_PAGE的功能,也就是某个标签返回SKIP_PAGE的时候,需要立即终止整个渲染。
现在来说另外一个问题,先举一个例子:
<c:out escapeXml="true"><div><c:out value="123"></c:out></div></c:out>
对于这个,它的输出结果是什么呢?正确的输出结果是这样的:
<div>123</div>
标签是怎么执行的呢?
首先第一层,第一个c:out没有var属性,也没有value属性,也没有defaultValue属性, 也就是说他要输出的是节点内的东西。
可以反编译一下tomcat或者resin的c:out的实现,发现他实现了BodyTagSupport. 再看一下源码,它还用了BodyContent.
BodyContent其实就是当前标签的内容,注意是渲染之后的内容。
也就是说当一个tag实现了BodyTag的时候,它就可以取得它的子标签渲染后的内容。这个功能的实现一开始不太好理解,其实很简单,看一下下面的代码:
public static void evaluate(final Node element, final PageContext pageContext){ Node node; TagEntry tagEntry; JspWriter out; JspWriter jspWriter = pageContext.getOut(); Stack<TagEntry> stack = new Stack<TagEntry>(); stack.push(new TagEntry(element, 0)); ExpressionContext expressionContext = pageContext.getExpressionContext(); while((tagEntry = stack.poll()) != null) { out = pageContext.getOut(); node = tagEntry.getNode(); try { if(node.getNodeType() == NodeType.TEXT) { out.write(node.toString()); continue; } if(node.getNodeType() == NodeType.COMMENT) { out.write(node.toString()); continue; } if(node.getNodeType() == NodeType.EXPRESSION) { Object value = expressionContext.evaluate(node.toString()); if(value != null) { out.write(value.toString()); } continue; } if(tagEntry.getStatus() == 0 || tagEntry.getStatus() == 2) { int flag = startTag(stack, tagEntry, pageContext, expressionContext); if(flag == Tag.SKIP_PAGE) { break; } } else { int flag = endTag(stack, tagEntry, pageContext); if(flag == Tag.SKIP_PAGE) { break; } } } catch(Exception e) { throw new RuntimeException(toString("Exception at ", node), e); } } pageContext.setOut(jspWriter);}/** * @param stack * @param tagEntry * @param pageContext */private static int startTag(Stack<TagEntry> stack, TagEntry tagEntry, PageContext pageContext, ExpressionContext expressionContext){ int flag = 0; Tag tag = tagEntry.getTag(); Node node = tagEntry.getNode(); if(tag == null) { tag = TagFactory.create(pageContext, node.getNodeName()); tagEntry.setTag(tag); tag.setPageContext(pageContext); if(tagEntry.getParent() != null) { tag.setParent(tagEntry.getParent().getTag()); } } int status = tagEntry.getStatus(); if(status == 0) { // create - doStartTag TagUtil.setAttributes(tag, node.getAttributes(), expressionContext); flag = tag.doStartTag(); tagEntry.setStartTagResult(flag); } else if(status == 1 || status == 2) { // to doEndTag flag = tagEntry.getStartTagResult(); } else { // illegal throw new IllegalStateException("Tag State Error !"); } tagEntry.setStatus(1); stack.push(tagEntry); if(flag == Tag.SKIP_PAGE) { return Tag.SKIP_PAGE; } if(flag != Tag.SKIP_BODY) { if(flag != Tag.EVAL_BODY_INCLUDE) { if(tag instanceof BodyTag) { BodyTag bodyTag = (BodyTag)tag; BodyContent bodyContent = (BodyContent)(pageContext.pushBody()); bodyTag.setBodyContent(bodyContent); bodyTag.doInitBody(); } } List<Node> childNodes = node.getChildNodes(); List<TagEntry> childEntrys = tagEntry.getChildNodes(); if(childNodes.size() > 0 && childEntrys.size() < 1) { for(int i = 0, size = childNodes.size(); i < size; i++) { TagEntry childTagEntry = new TagEntry(childNodes.get(i), 0); childTagEntry.setParent(tagEntry); childEntrys.add(childTagEntry); } } if(childEntrys.size() > 0) { for(int i = childEntrys.size() - 1; i > -1; i--) { TagEntry childEntry = childEntrys.get(i); childEntry.setStatus(0); stack.push(childEntry); } } } return flag;}/** * @param stack * @param tagEntry * @param pageContext */private static int endTag(Stack<TagEntry> stack, TagEntry tagEntry, PageContext pageContext){ Tag tag = tagEntry.getTag(); IterationTag iterationTag = null; if(tag instanceof IterationTag) { iterationTag = (IterationTag)tag; } if(iterationTag != null) { int flag = iterationTag.doAfterBody(); if(flag == BodyTag.EVAL_BODY_AGAIN) { tagEntry.setStatus(2); stack.push(tagEntry); return flag; } else { int startTagResult = tagEntry.getStartTagResult(); if(startTagResult != Tag.SKIP_BODY) { if(startTagResult != Tag.EVAL_BODY_INCLUDE) { if(tag instanceof BodyTag) { pageContext.popBody(); } } } } } tag.release(); return tag.doEndTag();}现在我们的模板引擎已经支持完整的jstl的tag功能了,而且我们避免了使用递归来渲染模板,性能还不错。
不过目前的实现仍然有不好的地方,编译有点慢,虽然我们可以使用缓存的机制确保它编译一次多次运行,在编译大文件的时候编译速度还是不太满意
而且程序结构有点复杂。
下一篇我们将重新设计一种类汇编的执行方式,编译速度将会有很大的提高,执行速度也将得到提升,同时代码结构也变得简单易读。