打造一个自己的模板引擎(三)
上一篇我们用栈的方式重新实现了渲染器,并且基本参照jstl的规范实现了tag标签,目前它可以工作的很好。
现在我们要对它进行重新设计,完全抛弃之前的设计。之前的方式在性能上不太好,而且代码也显得有点复杂。
先考虑汇编是怎么运行的:
系统启动之后,cpu指针寄存器会默认指向到一个位置,记不清地址了,假设是0x800吧,然后开始执行指令, 指令是顺序存放的
执行完一条执行下一条, 假设有一个循环,起始地址是0x5000, 循环结束地址是0x5010,那么这个循环的最后一句一般是跳转到循环的开始地址。
其实就是写指针寄存器的值,就是把指针寄存器的值赋值为0x5000.
总之,cpu有一个寄存器,寄存器中存的是下一条指令的地址. cpu每次都先读这个地址,然后根据这个地址找到指令去执行。
接下来我们就用类似的方式重新设计,解析的时候仍然需要用到栈,因为节点的父子关系还需要使用栈来确立,但是其他的节点就直接放到一个list里面,
这个list就好比是一个代码段,所有的指令都存在这个list里面,包括文本和节点。
每个节点里面保存一下它的偏移地址和长度。
现在抛弃xml吧,我们新的引擎和xml再也没有关系了。用指令和指针的方式来思考这个问题。
假设有如下一个list
1. <div> // 文本节点
2. <c:each items"1,2,3"> // 标签节点
3. <div>hello</div> // 文本节点
4. </c:each>
现在开始执行:
第一条指令,是个文本,直接输出,指针加1
第二条指令,是个节点,按照前篇的方式先执行doStartTag, 然后执行指针加1
第三条指令, 是个文本,直接输出,指针加1
第四条指令,是个结束节点,执行doAfterBody, 发现可以继续执行,修改指针到改节点的起始地址, 此时指针指向了2
第五条指令,此时指针指向2,因此继续指导第四条指令返回SKIP_BODY.
好了,接下来修改编译器,只需要编译出来一个list即可。任何节点都直接往list里面add即可。遇到标签节点的时候需要使用栈建立以下父子关系
父子关系可以使用一个节点引用,也可以使用一个整形变量指向这个节点的地址。
剩下的就是循环这个list,把他们当成一条条的指令执行就可以了,需要注意的是,由于我们的引擎大部分时候都运行在多线程环境下,
因此在执行过程中绝对不能对元素做任何修改,但是在运行过程中需要保存一些临时变量,例如tag的执行结果,还有tag创建了需要和节点关联,
因此需要重新创建一个新的list,每一个元素用一个新的类来表示,下面是渲染器的主要代码:
public static void execute(final List<Node> list, final PageContext pageContext){ Node node = null; JspWriter out = null; Statement statement = null; JspWriter jspWriter = pageContext.getOut(); List<Statement> statements = getStatements(list); ExpressionContext expressionContext = pageContext.getExpressionContext(); try { int flag = 0; int index = 0; int size = statements.size(); while(index < size) { out = pageContext.getOut(); statement = statements.get(index); node = statement.getNode(); if(node.getNodeType() == NodeType.TEXT) { out.write(node.toString()); index++; continue; } if(node.getNodeType() == NodeType.EXPRESSION) { Object value = expressionContext.evaluate(node.toString()); if(value != null) { out.write(value.toString()); } index++; continue; } if(node.getLength() == 0) { throw new RuntimeException(toString("Exception at ", node)); } if(node.getOffset() == index) { flag = doStartTag(statement, pageContext); if(flag == Tag.SKIP_BODY) { index = node.getOffset() + node.getLength(); continue; } if(flag == Tag.SKIP_PAGE) { break; } } else { flag = doEndTag(statement, pageContext); if(flag == BodyTag.EVAL_BODY_AGAIN) { index = node.getOffset() + 1; continue; } if(flag == Tag.SKIP_PAGE) { break; } } index++; } } catch(Throwable t) { throw new RuntimeException(toString("Exception at ", node), t); } pageContext.setOut(jspWriter); try { jspWriter.flush(); } catch(IOException e) { }}/** * @param statement * @return int */public static int doStartTag(final Statement statement, final PageContext pageContext){ Tag tag = statement.getTag(); Node node = statement.getNode(); if(tag == null) { tag = TagFactory.create(pageContext, node.getNodeName()); tag.setPageContext(pageContext); statement.setTag(tag); Statement parent = statement.getParent(); if(parent != null) { tag.setParent(parent.getTag()); } } // create - doStartTag TagUtil.setAttributes(tag, node.getAttributes(), pageContext.getExpressionContext()); int flag = tag.doStartTag(); statement.setStartTagFlag(flag); 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(); } } } return flag;}/** * @param statement * @param pageContext * @return int */private static int doEndTag(final Statement statement, final PageContext pageContext){ Tag tag = statement.getTag(); IterationTag iterationTag = null; if(tag instanceof IterationTag) { iterationTag = (IterationTag)tag; } if(iterationTag != null) { int flag = iterationTag.doAfterBody(); if(flag == BodyTag.EVAL_BODY_AGAIN) { return flag; } else { int startTagFlag = statement.getStartTagFlag(); if(startTagFlag != Tag.SKIP_BODY) { if(startTagFlag != Tag.EVAL_BODY_INCLUDE) { if(tag instanceof BodyTag) { pageContext.popBody(); } } } } } int flag = tag.doEndTag(); tag.release(); return flag;}