本文共 12163 字,大约阅读时间需要 40 分钟。
Tiny模板引擎的实现方式原来是采用的编译方式,最近发生了一些问题,因此我觉得有必要把编译方式调整为解释方式,为此就开始了此次实现活动。
当时采用编译方式,主要是考虑到编译方式在运行时不必再去遍历语法树,因此就采用了编译方式。但是在实际应用当中,出现了如下问题:
由于采用的是编译方式,这个时候就存在在一个选择,即:Java源代码落地或不落地的选择。如果Java文件不落地,则在有问题的时候,如果想要进行代码调试(虽然这种场景并不多见),那么就没有源代码可供调试。如果Java代码落地,则会存在一个问题,那就是资源文件在磁盘文件中产生冲突的问题。
同样的问题对于class文件也存在,如果不落地,那么每次应用重启动的时候,都要重新编译这些文件以产生class文件;如果落地,则也会产生冲突的问题。
当然,Tiny模板引擎通过增加一个配置项,解决了这个冲突的问题,但是由于增加了一个配置项,从客观上增加了维护人员的工作量,也容易造成当维护人员不了解这里面的道道,忘记设置从而导致在一台服务器中部署多个Tiny应用时多个应用中的模板文件生成的java文件和class文件的冲突,从而导致出现问题。
采用编译方式的时候,由于每个模板文件都要生成一个类,每个宏也要生成一个类,在宏调用过程中,也要生成一些类。(本来是可以不生成这些类的,但是由于Tiny模板引擎支持了一些非常有用的特性,所以宏调用时时采用编译方式,就要生成一些内嵌类来完成)。这样,就会生成大量的Java类,从工程非常大的时候,就会导致PermSize战胜非常大。尤其是在系统还在调试的时候,模板文件变化的时候,就要重新编译生成新的类,为了避免必须重新启动应用服务器才能生生效,因此采用了自己编写ClassLoader的方式来达到即时刷新的问题,但是由于Java的垃圾回收机制,决定了垃圾不是及时回收的,但是由于每个类都要有一个ClassLoader来支持,以便及时替换,因此这会进一步放大内存的占用。
由于Tiny模板引擎中提供了宏,而这些宏可以独立存在,因此在应用启动的时候就必须加载所有的宏到内存中,以便查找。所以就导致第一次启动的时候,由于要编译所有的宏文件并加载之,导致启动速度非常慢。在以后的启动的时候,也必须检测模板文件与生成的类是否一致,是否有被修改过,当a项目规模比较大的时候,这个时间也是比较长的。尤其是在开发期,启动时间增加10秒,都会让开发人员感觉到难以忍受。
采用编译方式的问题,在访问上也有一些问题。
为了提升应用启动时间,只有宏文件是在启动时预选编译好并加载了的,而模板文件和布局文件则没有这种待遇,这就导致如果在访问的时候,第一次访问的时候,需要编译模板文件为java文件,再把java文件编译为class文件,如果这次访问还用到了布局文件,还import了其它的模板文件,那么悲剧了,第一个访问者可能要多等待几秒钟的时间。同时,为了避免多次编译情况的地生,还要增加同步锁,这样会进一步影响到访问的效率。
具体还没有测试过ClassLoader太多对性能有多大的影响,但是毛估估是有一定影响的,毕竟要增加查找的层数。干的活多了,干的活慢了也是自然的,人是这样,计算机也是同样的道理。
由于采用解释方式,因此不必生成java源文件和class文件,因此也就不存在文件路径冲突的问题;同样也不存在PermSize和众多ClassLoader大量占用内存的问题。
由于采用解释方式,第一次加载,只定性扫描部分关系的内容即可,因此扫描速度非常快;只有在直接执行的时候,才需要更详细的处理,同时由于不需要进行编译,不需要做同步处理,因此加载速度会比编译方式高许多,尤其是和编译方式的第一次加载时间相比。
访问速度方面的问题,我原来的感觉来说,感觉编译方式会快一些,毕竟它不用再云遍历语法树,但是实际执行下来,感觉解释方式大致有一倍左右的提升,我分析了一下原因,大致可以认为是如下原因:1.由于Java的优化策略,导致使用频率高的访问会进行深度性能优化,采用解释方式,由于用到的就是那几个函数,因此可以很快满足Java虚拟机的要求,更早的进行深度优化;2.由于解释方式和编译方式相比,可以采用更优化的解决方案,因此遍历语法树的时间由避免做一些事情弥补回来了,因此感受性能反而更高一点点。总之,这次编译改解释,的效果还是明显的,各方面全面让我满意,尤其是前面担心的执行效率方面也有大概50%左右的提升是让我喜出望外的。还有一个意外之喜是通过把编译方式改成解释执行方式,代码规模缩小了近一半,由原来的8000+行,变成4000+行。同时,由于不必要依赖JDT,antlr也只要依赖runtime包即可,还顺便减少了3M的WAR包大小。
OK,说了这么多,那就说说这次改造过程。
由于团队去岛国旅游,当时把这个任务交给一个留守同学来完成,但是前后两周的时候,没有提交出我满意的结果,由于看不到后续完成的时间节点,没有办法,只好我老先生亲自动手来完成了,OK开工,相信仔细阅读下面一节内容的同学,会对ANTLR解释引擎的开发有深入了解,甚至拿我的代码照葫芦画瓢,直接就可用。
解释引擎总控类是解释引擎的核心,由于这个东东是为了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 | 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) throws Exception { interpret(engine, templateFromContext, parserTemplateTree(sourceName, templateString), pageContext, context, writer); writer.flush(); } public void interpret(TemplateEngineDefault engine, TemplateFromContext templateFromContext, TinyTemplateParser.TemplateContext templateParseTree, TemplateContext pageContext, TemplateContext context, Writer writer) throws Exception { for ( int i = 0 ; i < templateParseTree.getChildCount(); i++) { interpretTree(engine, templateFromContext, templateParseTree.getChild(i), pageContext, context, writer); } } public Object interpretTree(TemplateEngineDefault engine, TemplateFromContext templateFromContext, ParseTree tree, TemplateContext pageContext, TemplateContext context, Writer writer) 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) { ContextProcessor processor = contextProcessorMap.get(tree.getClass()); if (processor != null ) { returnValue = processor.process( this , templateFromContext, (ParserRuleContext) tree, pageContext, context, engine, writer); } 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); if (value != null ) { returnValue = value; } } } } else { for ( int i = 0 ; i < tree.getChildCount(); i++) { Object value = interpretTree(engine, templateFromContext, tree.getChild(i), pageContext, context, writer); 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()); writer.flush(); } } } |
所以逻辑还是比较清晰,最复杂的核心算法也只有30行,不管是什么样层级的同学,看这些代码都没有任何难度了。
需要交待的一件事情是:为什么ContextProcessor的处理类是用Map保存的,而TerminalNodeProcessor则是用数组?这里主要是为了考虑到TerminalNode都有一个类型,用数据的方式速度更快一些。
上面说到有两个接口,一个是处理TerminalNodeProcessor,另外一个是处理ContextProcessor的,下面交待一下这两个接口。
1 2 3 4 | public interface TerminalNodeProcessor<T extends ParseTree> { int getType(); Object process(T parseTree, TemplateContext context, Writer writer) throws Exception; } |
1 2 3 4 5 6 7 8 | public interface ContextProcessor<T extends ParserRuleContext> { Class<T> getType(); boolean processChildren(); Object process(TemplateInterpreter interpreter, TemplateFromContext templateFromContext, T parseTree, TemplateContext pageContext, TemplateContext context, TemplateEngineDefault engine, Writer writer) throws Exception; } |
至此,整个解析引擎的框架就搭好了,剩下要做的就是去写这些处理器了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class DoubleNodeProcessor implements TerminalNodeProcessor<TerminalNode> { public int getType() { return TinyTemplateParser.FLOATING_POINT; } public boolean processChildren() { return false ; } public Object process(TerminalNode terminalNode, TemplateContext context, Writer writer) { String text=terminalNode.getText(); return Double.parseDouble(text); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class StringDoubleNodeProcessor implements TerminalNodeProcessor<TerminalNode> { public int getType() { return TinyTemplateParser.STRING_DOUBLE; } public boolean processChildren() { return false ; } public Object process(TerminalNode terminalNode, TemplateContext context, Writer writer) { String text=terminalNode.getText(); text=text.replaceAll( "\\\\\"" , "\"" ); text=text.replaceAll( "[\\\\][\\\\]" , "\\\\" ); return text.substring( 1 , text.length() - 1 ); } } |
其它的和这个大同小异,总之非常简单,想看的同学可以自己去看源码,这里就不贴了。
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 | 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) throws Exception { String name = parseTree.for_expression().IDENTIFIER().getText(); Object values = interpreter.interpretTree(engine, templateFromContext, parseTree.for_expression().expression(),pageContext, context, writer); 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); } 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); } } return null ; } } |
是不是非常简单?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class MapProcessor implements ContextProcessor<TinyTemplateParser.Expr_hash_mapContext> { public Class<TinyTemplateParser.Expr_hash_mapContext> getType() { return TinyTemplateParser.Expr_hash_mapContext. class ; } public boolean processChildren() { return false ; } public Object process(TemplateInterpreter interpreter, TemplateFromContext templateFromContext, TinyTemplateParser.Expr_hash_mapContext parseTree, TemplateContext pageContext, TemplateContext context, TemplateEngineDefault engine, Writer writer) throws Exception { List<TinyTemplateParser.ExpressionContext> expressions = parseTree.hash_map_entry_list().expression(); List<TinyTemplateParser.ExpressionContext> expressionContexts = expressions; Map<String, Object> map = new HashMap<String, Object>(); if (expressions != null ) { for ( int i = 0 ; i < expressions.size(); i += 2 ) { String key = interpreter.interpretTree(engine, templateFromContext, expressions.get(i), pageContext,context, writer).toString(); Object value = interpreter.interpretTree(engine, templateFromContext, expressions.get(i + 1 ),pageContext, context, writer); map.put(key, value); } } return map; } } |
我已经拿了最复杂的两个来讲了,其它的就更简单了,因此就不再贴了,关心的同学们可以去看源代码。
有关Tiny框架的内容,请访问官网:
Tiny在线文档网站,请访问:
如果想获取Tiny的源代码,请访问:直接fork代码,喜欢的话,请点击watch和star关注我们的动态,支持我们。
如果你喜欢我的博客,也可以在查看到我写的更多的博客,必须精品;也可以到关注我,这样我的博客就可以第一时间推送给也,也可以看到我发的动态,绝对没有 连早晨起来解个小手也要发一下的内容,要发的必须是与Tiny息息相关的内容。
转载地址:http://epxax.baihongyu.com/