加载模板文件并编译的调用过程

TemplateCompiler.generate函数最终执行模板编译。从下往上看,可以看出该函数的调用路径:
11152601-f1c2876538aa45dda504313eccbc87a9.png
 
 
 
 
TemplateCompiler.generate: 编译并将内容写入(StringBuilder) compliedSource,最后执行template.compiledSource = compiledSource.toString()。编译过程是将html模板转换成Groovy语句,最终在GroovyTemplate.internalRender中调用GroovyTemplate.compile将该Groovy语句编译并封装成GroovyTemplate.compiledTemplate并缓存起来,在internalRender中调用执行:
ExecutableTemplate t = (ExecutableTemplate) InvokerHelper.createScript(compiledTemplate, binding);
...
t.run();
在binding中传入了包装过了的输出流:
binding.setProperty("out", new PrintWriter(writer));
 
在执行完成之后,internalRender返回输出流的内容:
        if (writer != null) {
            return writer.toString();
        }
        return null;
 
TemplateCompiler.generate代码如下:
void generate(BaseTemplate template) {
        this.template = template;
        String source = source();
        this.parser = new TemplateParser(source);
        // Class header
        head();
        // Parse
        loop:
        for (;;) {
            if (doNextScan) {
                state = parser.nextToken();
            } else {
                doNextScan = true;
            }
            switch (state) {
                case EOF:
                    break loop;
                case PLAIN:
                    plain();
                    break;
                case SCRIPT:
                    script();
                    break;
                case EXPR:
                    expr();
                    break;
                case MESSAGE:
                    message();
                    break;
                case ACTION:
                    action(false);
                    break;
                case ABS_ACTION:
                    action(true);
                    break;
                case COMMENT:
                    skipLineBreak = true;
                    break;
                case START_TAG:
                    startTag();
                    break;
                case END_TAG:
                    endTag();
                    break;
            }
        }
        // Class end
        end();
        // Check tags imbrication
        if (!tagsStack.empty()) {
            Tag tag = tagsStack.peek();
            throw new TemplateCompilationException(template, tag.startLine, "#{" + tag.name + "} is not closed.");
        }
        // Done !
        template.compiledSource = compiledSource.toString();
        if (Logger.isTraceEnabled()) {
            Logger.trace("%s is compiled to %s", template.name, template.compiledSource);
        }
    }
 
token的种类在TemplateParser中定义:
TemplateParser:enum Token
public enum Token {
 
        EOF, //
        PLAIN, //
        SCRIPT, // %{...}% or {%...%}
        EXPR, // ${...}
        START_TAG, // #{...}
        END_TAG, // #{/...}
        MESSAGE, // &{...}
        ACTION, // @{...}
        ABS_ACTION, // @@{...}
        COMMENT, // *{...}*
    }
TemplateParser进行语法分析,扫描模板文件内容,根据当前token类型与接下来的一个字符判断接下来的token类型。此部分涉及编译原理相关内容,可参看《Compilers:Principles,Techniques,and Tools》(作者:Alfred V.Aho,Ravi Sethi,Jeffrey D.Ullman )
 
在GroovyTemplateCompiler中实现了其中的虚函数:
head(): 开始声明一个类,继承自play.templates.GroovyTemplate.ExecutableTemplate,并声明并准备实现父类的虚函数:public Object run()
void head() {
        print("class ");
        //This generated classname is parsed when creating cleanStackTrace.
        //The part after "Template_" is used as key when
        //looking up the file on disk this template-class is generated from.
        //cleanStackTrace is looking in TemplateLoader.templates
 
        String uniqueNumberForTemplateFile = TemplateLoader.getUniqueNumberForTemplateFile(template.name);
 
        String className = "Template_" + uniqueNumberForTemplateFile;
        print(className);
        println(" extends play.templates.GroovyTemplate.ExecutableTemplate {");
        println("public Object run() { use(play.templates.JavaExtensions) {");
        for (String n : extensionsClassnames) {
            println("use(_('" + n + "')) {");
        }
    }
 
plain(): 将纯文本内容读取并写入输出流中:println("out.print(\""+text+"\");"); ——这里的println函数是在类内部实现的,负责将Groovy脚本写入compiledSource中,而不是System.out.println()——如果纯文本内容太大,将会分段写入:
void plain() {
...
    if (text.length() <maxPlainTextLength) {
            // text is "short" - just print it
            println("out.print(\""+text+"\");");
        } else {
            // text is long - must split it
            int offset = 0;
            do {
                int endPos = offset+maxPlainTextLength;
                if (endPos>text.length()) {
                    endPos = text.length();
                } else {
                    // #869 If the last char (at endPos-1) is \, we're dealing with escaped char - must include the next one also..
                    if ( text.charAt(endPos-1) == '\\') {
                        // use one more char so the escaping is not broken. Don't have to check length, since
                        // all '\' is used in escaping, ref replaceAll above..
                        endPos++;
                    }
                }
                println("out.print(\""+text.substring(offset, endPos)+"\");");
                offset+= (endPos - offset);
            }while(offset < text.length());
        }
}
 
startTag(): 为endTag构造一个Tag实例并保存在栈中,获取tag的参数(tag名空格后面部分),判断是否有body:boolean hasBody = !parser.checkNext().endsWith("/")。判断hasBody进行输出。如果hasBody为true,则接下来进入plain函数,将body部分输出:
void startTag() {
    ...
    boolean hasBody = !parser.checkNext().endsWith("/");//如果tag标签没有立即关闭,则说明有body
    ...   
    Tag tag = new Tag();
    tag.name = tagName;
    tag.startLine = parser.getLine();
    tag.hasBody = hasBody;
    tagsStack.push(tag);
    ...    
    if (!tag.name.equals("doBody") && hasBody) {
            print("body" + tagIndex + " = {");
            markLine(parser.getLine());
            println();
        } else {
            print("body" + tagIndex + " = null;");
            markLine(parser.getLine());
            println();
        }
    ...
}
 
endTag(): 从栈中获取tag实例,判断模板文件中tag变迁是否正常关闭。在Groovy脚本(TemplateCompiler.compiledSource)中执行tag相关操作:
void endTag() {
    
    String tagName = parser.getToken().trim();
        if (tagsStack.isEmpty()) {
            throw new TemplateCompilationException(template, parser.getLine(), "#{/" + tagName + "} is not opened.");
        }
        Tag tag = tagsStack.pop();
        String lastInStack = tag.name;
        if (tagName.equals("")) {
            tagName = lastInStack;
        }
        if (!lastInStack.equals(tagName)) {
            throw new TemplateCompilationException(template, tag.startLine, "#{" + tag.name + "} is not closed.");
        }
    ...
    
            if (tag.hasBody) {
                print("};"); // close body closure
            }
            println();
    ...
    
        print("play.templates.TagContext.enterTag('" + tag.name + "');");
                    print("_('" + m.getDeclaringClass().getName() + "')._" + tName + "(attrs" + tagIndex + ",body" + tagIndex + ", out, this, " + tag.startLine + ");");
                    print("play.templates.TagContext.exitTag();");
}
 
expr(): 提取表达式并调用ExecutableTemplate.__safeFaster(expr):
void expr() {
        String expr = parser.getToken().trim();
        print(";out.print(__safeFaster("+expr+"))");
        markLine(parser.getLine());
        println();
    }
 
message(): 提取消息并调用ExecutableTemplate.__getMessage(expr)。__getMessage(expr)函数负责搜索并组装制定的消息:
void message() {
        String expr = parser.getToken().trim();
        print(";out.print(__getMessage("+expr+"))");
        markLine(parser.getLine());
        println();
    }
 
action(): 读取action内容,在脚本中调用负责将action转换为URL的相应的函数:
void action(boolean absolute) {
        String action = parser.getToken().trim();
        if (action.trim().matches("^'.*'$")) {
            if (absolute) {
                print("\tout.print(__reverseWithCheck_absolute_true("+action+"));");
            } else {
                print("\tout.print(__reverseWithCheck_absolute_false("+action+"));");
            }
        } else {
            if (!action.endsWith(")")) {
                action = action + "()";
            }
            if (absolute) {
                print("\tout.print(actionBridge._abs()." + action + ");");
            } else {
                print("\tout.print(actionBridge." + action + ");");
            }
        }
        markLine(parser.getLine());
        println();
    }
 
script(): 模板中的脚本可以作为Groovy脚本直接运行,所以该函数将其直接提取出来写入脚本compiledSource中。对于多行的脚本,将分行写入:
void script() {
        String text = parser.getToken();
        if (text.indexOf("\n") > -1) {
            String[] lines = parser.getToken().split("\n");
            for (int i = 0; i < lines.length; i++) {
                print(lines[i]);
                markLine(parser.getLine() + i);
                println();
            }
        } else {
            print(text);
            markLine(parser.getLine());
            println();
        }
        skipLineBreak = true;
    }
 
end(): 将类声明关闭:
void end() {
        for (String n : extensionsClassnames) {
            println(" } ");
        }
        println("} }");
        println("}");
    }
 
Template有了编译成groovy语言的compiledSource 之后便可以开始执行了【?】
 
 
编译完成的例子:
class Template_1002 extends play.templates.GroovyTemplate.ExecutableTemplate {
public Object run() { use(play.templates.JavaExtensions) {
out.print("");
attrs1 = [arg:'main.html'];body1 = null;// line 1
 
play.templates.TagContext.enterTag('extends');_('play.templates.FastTags')._extends(attrs1,body1, out, this, 1);play.templates.TagContext.exitTag();// line 1
out.print("\n");
attrs1 = [title:'进货管理'];body1 = null;// line 2
 
play.templates.TagContext.enterTag('set');_('play.templates.FastTags')._set(attrs1,body1, out, this, 2);play.templates.TagContext.exitTag();// line 2
out.print("\n");
attrs1 = [arg:actionBridge.Application.stockCommit()];body1 = {// line 3
out.print("\n    <p>\n        <label for=\"barCode\">请输入条形码</label>\n        <input type=\"text\" name=\"barCode\" />\n    </p>\n    <p>\n        <label for=\"cost\">请输入商品名</label>\n        <input type=\"text\" name=\"name\"/>\n    </p>\n    <p>\n        <label for=\"cost\">请输入进货价</label>\n        <input type=\"text\" name=\"cost\"/>\n    </p>    \n    <p>\n        <label for=\"price\">请输入进售价</label>\n        <input type=\"text\" name=\"price\"/>\n    </p>\n    <p>\n        <label for=\"stock\">请输入进数量</label>\n        <input type=\"text\" name=\"stock\"/>\n    </p>\n    <p>\n        <input type=\"submit\" value=\"确定\" />\n    </p>\n");
};
play.templates.TagContext.enterTag('form');_('play.templates.FastTags')._form(attrs1,body1, out, this, 3);play.templates.TagContext.exitTag();// line 3
out.print("\n<br/><a href=\"");
    out.print(actionBridge.Application.index());// line 28
out.print("\">返回主界面</a>");
} }
}
 
模板内容为:
#{extends 'main.html' /}
#{set title:'进货管理' /}
#{form @Application.stockCommit()}
    <p>
        <label for="barCode">请输入条形码</label>
        <input type="text" name="barCode" />
    </p>
    <p>
        <label for="cost">请输入商品名</label>
        <input type="text" name="name"/>
    </p>
    <p>
        <label for="cost">请输入进货价</label>
        <input type="text" name="cost"/>
    </p>   
    <p>
        <label for="price">请输入进售价</label>
        <input type="text" name="price"/>
    </p>
    <p>
        <label for="stock">请输入进数量</label>
        <input type="text" name="stock"/>
    </p>
    <p>
        <input type="submit" value="确定" />
    </p>
#{/form}
<br/><a href="@{Application.index()}">返回主界面</a>
 
完成了将模板文件转换为Groovy脚本之后,在GroovyTemplate.compile()中使用Groovy的编译器完成脚本的编译,在GroovyTemplate.internalRender()中完成调用并执行。
GroovyTemplate.compile():
public void compile() {
        if (compiledTemplate == null) {
            try {
                //...
                final List<GroovyClass> groovyClassesForThisTemplate = new ArrayList<GroovyClass>();
                //开始Groovy编译环境设置
                CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
                compilerConfiguration.setSourceEncoding("utf-8"); // ouf
                CompilationUnit compilationUnit = new CompilationUnit(compilerConfiguration);
                //此处将Groovy脚本compiledSource写入
                compilationUnit.addSource(new SourceUnit(name, compiledSource, compilerConfiguration, tClassLoader, compilationUnit.getErrorCollector()));
                Field phasesF = compilationUnit.getClass().getDeclaredField("phaseOperations");
                phasesF.setAccessible(true);
                LinkedList[] phases = (LinkedList[]) phasesF.get(compilationUnit);
                LinkedList<GroovyClassOperation> output = new LinkedList<GroovyClassOperation>();
                phases[Phases.OUTPUT] = output;
                output.add(new GroovyClassOperation() {
                    public void call(GroovyClass gclass) {
                        groovyClassesForThisTemplate.add(gclass);
                    }
                });
                //编译
                compilationUnit.compile();
                // ouf
                //将编译好的类序列化
                // Define script classes
                StringBuilder sb = new StringBuilder();
                sb.append("LINESMATRIX" + "\n");
                sb.append(Codec.encodeBASE64(Java.serialize(linesMatrix)).replaceAll("\\s", ""));
                sb.append("\n");
                sb.append("DOBODYLINES" + "\n");
                sb.append(Codec.encodeBASE64(Java.serialize(doBodyLines)).replaceAll("\\s", ""));
                sb.append("\n");
                for (GroovyClass gclass : groovyClassesForThisTemplate) {
                    tClassLoader.defineTemplate(gclass.getName(), gclass.getBytes());
                    sb.append(gclass.getName());
                    sb.append("\n");
                    sb.append(Codec.encodeBASE64(gclass.getBytes()).replaceAll("\\s", ""));
                    sb.append("\n");
                }
                //将序列化好的类写入缓存保存起来
                // Cache
                BytecodeCache.cacheBytecode(sb.toString().getBytes("utf-8"), name, source);
                //编译完成之后的第一个Groovy类就是代表了该模板的类,继承了play.templates.GroovyTemplate.ExecutableTemplate
                compiledTemplate = tClassLoader.loadClass(groovyClassesForThisTemplate.get(0).getName());
                if (System.getProperty("precompile") != null) {
                    try {
                        // emit bytecode to standard class layout as well
                        File f = Play.getFile("precompiled/templates/" + name.replaceAll("\\{(.*)\\}", "from_$1").replace(":", "_").replace("..", "parent"));
                        f.getParentFile().mkdirs();
                        FileOutputStream fos = new FileOutputStream(f);
                        fos.write(sb.toString().getBytes("utf-8"));
                        fos.close();
                    } catch (Exception e) {
                        Logger.warn(e, "Unexpected");
                    }
                }
 
                if (Logger.isTraceEnabled()) {
                    Logger.trace("%sms to compile template %s to %d classes", System.currentTimeMillis() - start, name, groovyClassesForThisTemplate.size());
                }
 
            } catch (MultipleCompilationErrorsException e) {
                if (e.getErrorCollector().getLastError() != null) {
                    SyntaxErrorMessage errorMessage = (SyntaxErrorMessage) e.getErrorCollector().getLastError();
                    SyntaxException syntaxException = errorMessage.getCause();
                    Integer line = this.linesMatrix.get(syntaxException.getLine());
                    if (line == null) {
                        line = 0;
                    }
                    String message = syntaxException.getMessage();
                    if (message.indexOf("@") > 0) {
                        message = message.substring(0, message.lastIndexOf("@"));
                    }
                    throw new TemplateCompilationException(this, line, message);
                }
                throw new UnexpectedException(e);
            } catch (Exception e) {
                throw new UnexpectedException(e);
            }
        }
        compiledTemplateName = compiledTemplate.getName();
    }
 
 
GroovyTemplate.internalRender: 定义输出流,将其绑定到Groovy环境中,反射调用之前编译好的Groovy类(compiledTemplate),最终返回输出流的内容:writer.toString()
    protected String internalRender(Map<String, Object> args){
        compile();
        //定义绑定变量:
        Binding binding = new Binding(args);
        binding.setVariable("play", new Play());
        binding.setVariable("messages", new Messages());
        binding.setVariable("lang", Lang.get());
        //...
        //定义输出流并绑定到环境变量中:     
        if (!args.containsKey("out")) {
            // This is the first template being rendered.
            // We have to set up the PrintWriter that this (and all sub-templates) are going
            // to write the output to..
            applyLayouts = true;
            layout.set(null);
            writer = new StringWriter();
            binding.setProperty("out", new PrintWriter(writer));
            currentTemplate.set(this);
        }
        //调用并执行模板类:    
        ExecutableTemplate t = (ExecutableTemplate) InvokerHelper.createScript(compiledTemplate, binding);
        t.init(this);
        try {
            monitor = MonitorFactory.start(name);
            long start = System.currentTimeMillis();
            t.run();
            monitor.stop();
            monitor = null;
            if (Logger.isTraceEnabled()) {
                Logger.trace("%sms to render template %s", System.currentTimeMillis() - start, name);
            }
        }
        //异常处理
        //...
        //完成:  
    if (writer != null) {
            return writer.toString();
        }
        return null;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 




转载于:https://www.cnblogs.com/UncleRiver/p/3469473.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值