TemplateCompiler.generate函数最终执行模板编译。从下往上看,可以看出该函数的调用路径:
![11152601-f1c2876538aa45dda504313eccbc87a9.png](https://i-blog.csdnimg.cn/blog_migrate/e7ef87e92fb8b87d41ab802dca90e630.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 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;
}