3.1. 配置GroupTemplate

Beetl建议通过配置文件配置配置GroupTemplate,主要考虑到未来可能IDE插件会支持Beetl模板,模板的属性,和函数等如果能通过配置文件获取,将有助于IDE插件识别。 配置GroupTemplate有俩种方法

  • 配置文件: 默认配置在/org/beetl/core/beetl-default.properties 里,Beetl首先加载此配置文件,然后再加载classpath里的beetl.properties,并用后者覆盖前者。配置文件通过Configuration类加载,因此加载完成后,也可以通过此类API来修改配置信息

  • 通过调用GroupTemplate提供的方法来注册函数,格式化函数,标签函数等

配置文件分为三部分,第一部分是基本配置,在第一节讲到过。第二部分是资源类配置,可以在指定资源加载类,以及资源加载器的属性,如下

1
2
3
4
5
6
RESOURCE_LOADER=org.beetl.core.resource.ClasspathResourceLoader
#资源配置,resource后的属性只限于特定ResourceLoader
#classpath 根路径
RESOURCE.root= /
#是否检测文件变化
RESOURCE.autouCheck= true

第一行指定了类加载器,第二行指定了模板根目录的路径,此处/ 表示位于classpath 根路径下,第三行是否自动检测模板变化,默认为true,开发环境下自动检测模板是否更改。关于如何如何自定义ResouceLoader,请参考下一章

配置文件第三部分是扩展部分,如方法,格式化函数等

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#####  扩展 ##############
## 内置的方法
FN.date = org.beetl.ext.fn.DateFunction
FN.nvl = org.beetl.ext.fn.NVLFunction
.................
##内置的功能包
FNP.strutil = org.beetl.ext.fn.StringUtil ##内置的格式化函数
FT.dateFormat = org.beetl.ext.format.DateFormat
FT.numberFormat = org.beetl.ext.format.NumberFormat
................. ##内置的默认格式化函数
FTC.java.util.Date = org.beetl.ext.format.DateFormat
FTC.java.sql.Date = org.beetl.ext.format.DateFormat ## 标签类
TAG.include= org.beetl.ext.tag.IncludeTag
TAG.includeFileTemplate= org.beetl.ext.tag.IncludeTag
TAG.layout= org.beetl.ext.tag.LayoutTag
TAG.htmltag= org.beetl.ext.tag.HTMLTagSupportWrapper

FN前缀表示Function,FNP前缀表示FunctionPackage,FT表示format函数,FTC表示类的默认Format函数,TAG表示标签类。Beetl强烈建议通过配置文件加载扩展。以便随后IDE插件能识别这些注册函数

3.2. 自定义方法

3.2.1. 实现Function

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Print implements Function
{ public String call(Object[] paras, Context ctx)
{
Object o = paras[0]; if (o != null)
{
try
{
ctx.byteWriter.write(o.toString());
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
return ""; }

call方法有俩个参数,第一个是数组,这是由模板传入的,对应着模板的参数,第二个是Context,包含了模板的上下文,主要提供了如下属性

  • byteWriter 输出流

  • template 模板本身

  • gt GroupTemplate

  • globalVar 该模板对应的全局变量

  • byteOutputMode 模板的输出模式,是字节还是字符

  • safeOutput 模板当前是否处于安全输出模式

  • 其他属性建议不熟悉的开发人员不要乱动

1 call方法要求返回一个Object,如果无返回,返回null即可

2 为了便于类型判断,call方法最好返回一个具体的类,如date函数返回的就是java.util.Date

3 call方法里的任何异常应该抛出成Runtime异常

3.2.2. 使用普通的java类

尽管实现Function对于模板引擎来说,是效率最高的方式,但考虑到很多系统只有util类,这些类里的方法仍然可以注册为模板函数。其规则很简单,就是该类的所有public方法。如果需还要Context 变量,则需要在方法最后一个参数加上Context即可,如

1
2
3
4
5
6
7
8
public class util
{ public String print(Object a, Context ctx)
{
............... }

注意

1 从beetl效率角度来讲,采用普通类效率不如实现Function调用

2 采用的普通java类尽量少同名方法。这样效率更低。beetl调用到第一个适合的同名方法。而不像java那样找到最匹配的

3 方法名支持可变数组作为参数

4 方法名最后一个参数如果是Context,则beetl会传入这个参数。

3.2.3. 使用模板文件作为方法

可以不用写java代码,模板文件也能作为一个方法。默认情况下,需要将模板文件放到Root的functions目录下,且扩展名为.html(可以配置文件属性来修改此俩默认值) 方法参数分别是para0,para1…..

如下root/functions/page.fn

1
2
3
4
5
<%
//para0,para1 由函数调用传入
var current = para0,total = para1,style=para2!'simple'
%>
当前页面 ${current},总共${total}

则在模板中

1
2
3
<%
page(current,total);
%>

允许使用return 表达式返回一个变量给调用者,如模板文件functions\now.html

1
2
3
<%
return date();
%>

在任何模板里都可以调用:

1
hello time is ${now(),‘yyyy-MM-dd’}

也可以在functions建立子目录,这样function则具有namespace,其值就是文件夹名

3.3. 自定义格式化函数

需要实现Format接口

 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
public class DateFormat implements Format
{ public Object format(Object data, String pattern)
{
if (data == null)
return null;
if (Date.class.isAssignableFrom(data.getClass()))
{
SimpleDateFormat sdf = null;
if (pattern == null)
{
sdf = new SimpleDateFormat();
}
else
{
sdf = new SimpleDateFormat(pattern);
}
return sdf.format((Date) data); }
else
{
throw new RuntimeException("Arg Error:Type should be Date");
}
}

data 参数表示需要格式化的对象,pattern表示格式化模式,开发时候需要考虑pattern为null的情况

也可以实现ContextFormat 类抽象方法,从而得到Context,获取外的格式化信息。

1
        public abstract Object format(Object data,String pattern,Context ctx);

3.4. 自定义标签

标签形式有俩种,一种是标签函数,第二种是html tag。第二种实际上在语法解析的时候会转化成第一种,其实现是HTMLTagSupportWrapper,此类将会寻找root/htmltag目录下同名的标签文件作为模板来执行。类似普通模板一样,在此就不详细说了

3.4.1. 标签函数

标签函数类似jsp2.0的实现方式,需要实现Tag类的render方法即可

 1
2
3
4
5
6
7
8
9
10
11
12
public class DeleteTag extends Tag
{ @Override
public void render()
{
// do nothing,just ignore body
ctx.byteWriter.write("被删除了,付费可以看") } }

如上一个最简单的Tag,将忽略tag体,并输出内容

 1
2
3
4
5
6
7
8
9
10
11
public class XianDeDantengTag extends Tag
{ @Override
public void render()
{
doBodyRender(); } }

此类将调用父类方法doBodyRender,渲染tag body体

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CompressTag extends Tag
{ @Override
public void render()
{
BodyContent content = getBodyContent();
String content = content.getBody();
String zip = compress(conent);
ctx.byteWriter.write(zip); } }

此类将调用父类方法getBodyContent ,获得tag body后压缩输出

tag类提供了如下属性和方法供使用

  • args 传入标签的参数

  • gt GroupTemplate

  • ctx Context

  • bw 当前的输出流

  • bs 标签体对应的语法树,不熟悉勿动

3.5. 自定义虚拟属性

可以为特定类注册一个虚拟属性,也可以为一些类注册虚拟属性

  • public void registerVirtualAttributeClass(Class cls, VirtualClassAttribute virtual) 实现VirtualClassAttribute方法可以为特定类注册一个需要属性,如下代码:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
gt.registerVirtualAttributeClass(User.class, new VirtualClassAttribute() {

        @Override
public String eval(Object o, String attributeName, Context ctx)
{ User user = (User) o;
if(attributeName.equals("ageDescritpion")){
if (user.getAge() < 10)
{
return "young";
}
else
{
return "old";
}
} } });

User类的所有虚拟属性将执行eval方法,此方法根据年纪属性来输出对应的描述。

  • public void registerVirtualAttributeEval(VirtualAttributeEval e) 为一些类注册需要属性,VirtualAttributeEval.isSupport方法将判断是否应用虚拟属性到此类

如下是虚拟属性类的定义

 1
2
3
4
5
6
7
8
9
10
11
12
public interface VirtualClassAttribute
{
public Object eval(Object o, String attributeName, Context ctx); } public interface VirtualAttributeEval extends VirtualClassAttribute
{ public boolean isSupport(Class c, String attributeName);
}

3.6. 使用额外的资源加载器

某些情况下,模板来源不止一处,GroupTemplate配置了一个默认的资源加载器,如果通过gt.getTemplate(key),将调用默认的ResourceLoader,获取模板内容,然后转化为beetl脚本放入到缓存里。你也可以传入额外的资源管理器加载模板,通过调用gt.getTemplate(key,otherLoader)来完成;

 1
2
3
4
5
6
7
8
9
10
11
GroupTemplate gt = new GroupTemplate(conf,fileLoader)
//自定义,参考下一节
MapResourceLoader dbLoader = new MapResourceLoader(getData());
Template t = gt.getTemplate("db:1", dbLoader); private Map getData()
{
Map data = new HashMap();
data.put("db:1", "${a}");
return data;
}

对于更复杂的模板资源来源,也可以自定义一个资源加载来完成,参考下一节

3.7. 自定义资源加载器

如果模板资源来自其他地方,如数据库,或者混合了数据库和物理文件,或者模板是加密的,则需要自定义一个资源加载器。资源加载器需要实现ResourceLoader类。如下:

 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 interface ResourceLoader
{ /**
* 根据key获取Resource
*
* @param key
* @return
*/
public Resource getResource(String key); /** 检测模板是否更改,每次渲染模板前,都需要调用此方法,所以此方法不能占用太多时间,否则会影响渲染功能
* @param key
* @return
*/
public boolean isModified(Resource key); /**
* 关闭ResouceLoader,通常是GroupTemplate关闭的时候也关闭对应的ResourceLoader
*/
public void close(); /** 一些初始化方法
* @param gt
*/
public void init(GroupTemplate gt); /** 用于include,layout等根据相对路径计算资源实际的位置.
* @param resource 当前资源
* @param key
* @return
*/
public String getResourceId(Resource resource, String key); }

如下是一个简单的内存ResourceLoader

 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
public class MapResourceLoader implements ResourceLoader
{
Map data; public MapResourceLoader(Map data)
{
this.data = data;
} @Override
public Resource getResource(String key)
{
String content = (String) data.get(key);
if (content == null)
return null;
return new StringTemplateResource(content, this);
} @Override
public boolean isModified(Resource key)
{
return false;
} @Override
public boolean exist(String key)
{ return data.contain(key);
} @Override
public void close()
{ } @Override
public void init(GroupTemplate gt)
{ } @Override
public String getResourceId(Resource resource, String id)
{
//不需要计算相对路径
return id;
}
}

init方法可以初始化GroupTemplate,比如读取配置文件的root属性,autoCheck属性,字符集属性,以及加载functions目录下的所有模板方法 如FileResourceLoader 的 init方法

 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
@Override
public void init(GroupTemplate gt)
{
Map<String, String> resourceMap = gt.getConf().getResourceMap();
if (this.root == null)
{
this.root = resourceMap.get("root"); }
if (this.charset == null)
{
this.charset = resourceMap.get("charset"); }
if (this.functionSuffix == null)
{
this.functionSuffix = resourceMap.get("functionSuffix");
} this.autoCheck = Boolean.parseBoolean(resourceMap.get("autoCheck")); File root = new File(this.root, this.functionRoot);
this.gt = gt;
if (root.exists())
{
readFuntionFile(root, "", "/".concat(functionRoot).concat("/"));
} }

readFuntionFile 方法将读取functions下的所有模板,并注册为方法

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected void readFuntionFile(File funtionRoot, String ns, String path)
{
String expected = ".".concat(this.functionSuffix);
File[] files = funtionRoot.listFiles();
for (File f : files)
{
if (f.isDirectory())
{
//读取子目录
readFuntionFile(f, f.getName().concat("."), path.concat(f.getName()).concat("/"));
}
else if (f.getName().endsWith(functionSuffix))
{
String resourceId = path + f.getName();
String fileName = f.getName();
fileName = fileName.substring(0, (fileName.length() - functionSuffix.length() - 1));
String functionName = ns.concat(fileName);
FileFunctionWrapper fun = new FileFunctionWrapper(resourceId);
gt.registerFunction(functionName, fun);
}
}
}

Resource类需要实现OpenReader方法,以及isModified方法。对于模板内容存储在数据库中,openReader返回一个Clob,isModified 则需要根据改模板内容对应的lastUpdate(通常数据库应该这么设计)来判断模板是否更改

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class Resource
{ /**
* 打开一个新的Reader
*
* @return
*/
public abstract Reader openReader(); /**
* 检测资源是否改变
*
* @return
*/
public abstract boolean isModified();

参考例子可以参考beetl自带的ResourceLoader

3.8. 使用CompositeResourceLoader

组合加载器,可以包含多个已有的ResourceLoader,如下代码创建一个包含俩个文件和内存的ResourceLoader

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
FileResourceLoader fileLoader1 = new FileResourceLoader(path1);
FileResourceLoader fileLoader2 = new FileResourceLoader(path2);
Map data = getData();
// 根据id加载
MapResourceLoader mapLoader = new MapResourceLoader(data); CompositeResourceLoader loader = new CompositeResourceLoader();
loader.addResourceLoader(new StartsWithMatcher("http:").withoutPrefix(), fileLoader2);
loader.addResourceLoader(new StartsWithMatcher("db:"), mapLoader);
loader.addResourceLoader(new AllowAllMatcher(), fileLoader1); GroupTemplate gt = new GroupTemplate(loader, conf);
Template t = gt.getTemplate("/xxx.html");

如上例子,groupTemplate从CompositeResourceLoader里加载/xxx.html,由于http:和db:前缀都不匹配,因此,将实际采用fileLoader1加载path1+/xxx.html,如下是xxx.html文件内容

1
2
3
4
<%
include("/xxx2.html"){}
include("http:/xxx.html"){}
%>

第2行仍然是由fileLoader1加载,但第3行以http:前缀开头,因此将fileLoader2加载path2+/xxx.html.xxx.html内容如下

1
2
3
<%
include("db:1"){}
%>

因为以db:开头,因此会采用MapResourceLoader加载,内容是key为db:1对模板

3.9. 自定义错误处理器

错误处理器需要实现ErrorHandler接口的processExcption(BeetlException beeExceptionos, Writer writer);

  • beeExceptionos,模板各种异常

  • writer 模板使用的输出流。系统自带的并未采用此Writer,而是直接输出到控制台

自定义错误处理可能是有多个原因,比如

1 想将错误输出到页面而不是控制台

2 错误输出美化一下,而不是自带的格式

3 错误输出的内容做调整,如不输出错误行的模板内容,而仅仅是错误提示

4 错误输出到日志系统里

5 不仅仅输出日志,还抛出异常。默认自带的不会抛出异常,ReThrowConsoleErrorHandler 继承了ConsoleErrorHandler方法,打印异常后抛出

 1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReThrowConsoleErrorHandler extends ConsoleErrorHandler
{ @Override
public void processExcption(BeetlException ex, Writer writer)
{ super.processExcption(ex, writer);
throw ex; } }

beetl 提供 ErrorInfo类来wrap BeetlException,转化为较为详细的提示信息,他具有如下信息

  • type 一个简单的中文描述

  • errorCode 内部使用的错误类型标识

  • errorTokenText 错误发生的节点文本

  • errorTokenLine 错误行

  • msg 错误消息,有可能没有,因为有时候errorCode描述的已经很清楚了

  • cause 错误的root 异常,也可能没有。

BeetlException 也包含了一个关键信息就是 resourceId,即出错所在的模板文件

3.10. 自定义安全管理器

所有模板的本地调用都需要通过安全管理器校验,默认需要实现NativeSecurityManager 的public boolean permit(String resourceId, Class c, Object target, String
method) 方法

如下是默认管理器的实现方法

 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
public class DefaultNativeSecurityManager implements NativeSecurityManager
{ @Override
public boolean permit(String resourceId, Class c, Object target, String method)
{
if (c.isArray())
{
//允许调用,但实际上会在在其后调用中报错。不归此处管理
return true;
}
String name = c.getSimpleName();
String pkg = c.getPackage().getName();
if (pkg.startsWith("java.lang"))
{
if (name.equals("Runtime") || name.equals("Process") || name.equals("ProcessBuilder")
|| name.equals("System"))
{
return false;
}
} return true;
} }

3.11. 注册全局共享变量

groupTemplate.setSharedVars(Map<String, Object> sharedVars)

3.12. 布局

布局可以通过Beetl提供的include,layout 以及模板变量来完成。模板变量能完成复杂的布局

  • 采用layout include

1
2
3
4
5
6
 <%
//content.html内容如下:
layout("/inc/layout.html"){%>
this is 正文
..........
<%}%>

如上一个子页面将使用layout布局页面,layout 页面内容如下

1
2
3
 <%include("/inc/header.html"){} %>
this is content:${layoutContent}
this is footer:

layoutContent 是默认变量,也可以改成其他名字,具体请参考layout标签函数

全局变量总是能被布局用的页面所使用,如果布局页面需要临时变量,则需要显示的传入,如:

1
2
3
 <%
var user= model.user;
include("/inc/header.html",{title:'这是一个测试页面',user:user}){} %>

这样,title和user成为全局变量,能被header.html 及其子页面引用到

  • 继承布局:采用模板变量和include

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 <%
var jsPart = {
%>
web页面js部分 <%};%> <%
var htmlPart = {
%>
web页面html部分 <%};
include("/inc/layout.html",{jsSection:jsPart,htmlSection:htmlPart}){}
%>

layout.html页面如下:

1
2
3
4
5
6
7
8
<body>
<head>
${jsSection}
</head>
<body>
.......
${htmlSection}
</body>

3.13. 性能优化

Beetl性能已经很快了,有些策略能更好提高性能

  • 使用二进制输出,此策略可以使模板在语法分析的时候将静态文本转化为二进制,省去了运行时刻编码时间,这是主要性能提高方式。但需要注意,此时需要提供一个二进制输出流,而不是字符流,否则性能反而下降

  • 使用FastRuntimeEngine,默认配置。 此引擎能对语法树做很多优化,从而提高运行性能,如生成字节码来访问属性而不是传统的反射访问。关于引擎,可能在新的版本推出更好的引擎,请随时关注。

  • 通过@type 来申明全局变量类型,这不能提高运行性能,但有助于模板维护

  • 自定义ResourceLoader的isModified必须尽快返回,因此每次渲染模板的时候都会调用此方法

为什么Beetl性能这么好…………(待续)

3.14. 分布式缓存模板

Beetl模板引擎模板在同一个虚拟机里缓存Beetl 脚本。也可以将缓存脚本到其他地方,只要实现Cache接口,并设置ProgramCacheFactory.cache即可,这样GroupTemplate将从你提供的Cache中存取Beetl脚本

此功能未被很好测试

3.15. 定制输出

占位符输出允许定制。如所有日期类型都按照某个格式化输出,而不需要显示的使用格式化输出,或者为了防止跨脚本站点攻击,需要对类型为String的值做检查等,不必使用格式化函数,可以直接对占位符输出进行定制,代码如下

 1
2
3
4
5
6
7
8
9
10
PlaceholderST.output = new PlaceholderST.Output(){

                                        @Override
public void write(Context ctx, Object value) throws IOException {
//定制输出
ctx.byteWriter.writeString("ok"+value!=null?value.toString:""); } };

如果PlaceholderST静态变量output 不为null,将使用output 来输出

3.16. 定制模板引擎

Beetl在线体验(http://ibeetl.com:8080/beetlonline/)面临一个挑战,允许用户输入任何脚本做练习或者分享代码。但又需要防止用户输入恶意的代码,如

1
2
3
4
5
<%
for(var i=0;i<10000000;i++){
//其他代码
}
%>

此时,需要定制模板引擎,遇到for循环的时候,应该限制循环次数,譬如,在线体验限制最多循5次,这是通过定义替换GeneralForStatement类来完成的,这个类对应了for(exp;exp;exp) ,我们需要改成如下样子:

 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
77
78
79
80
81
82
83
84
85
86
87
class RestrictForStatement extends GeneralForStatement
{
public RestrictForStatement(GeneralForStatement gf)
{
super(gf.varAssignSeq, gf.expInit, gf.condtion, gf.expUpdate, gf.forPart, gf.elseforPart, gf.token);
} public void execute(Context ctx)
{
if (expInit != null)
{
for (Expression exp : expInit)
{
exp.evaluate(ctx);
}
}
if (varAssignSeq != null)
{
varAssignSeq.execute(ctx);
} boolean hasLooped = false;
int i = 0;
for (; i < 5; i++)
{
boolean bool = (Boolean) condtion.evaluate(ctx);
if (bool)
{
hasLooped = true;
forPart.execute(ctx);
switch (ctx.gotoFlag)
{
case IGoto.NORMAL:
break;
case IGoto.CONTINUE:
ctx.gotoFlag = IGoto.NORMAL;
continue;
case IGoto.RETURN:
return;
case IGoto.BREAK:
ctx.gotoFlag = IGoto.NORMAL;
return;
} }
else
{
break;
} if (this.expUpdate != null)
{
for (Expression exp : expUpdate)
{
exp.evaluate(ctx);
}
} } if (i >= 5)
{
try
{
ctx.byteWriter.writeString("--Too may Data in loop,Ignore the left Data for Online Engine--");
ctx.byteWriter.flush(); }
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
} } @Override
public void infer(InferContext inferCtx)
{
super.infer(inferCtx); } }

尽管上面代码很复杂,但实际上是改写了原来的GeneralForStatement,将原来的24行while(true) 替换成for (; i < 5; i++) 用来控制最大循环,并且62行检测如果循环退出后,i等于5,则提示Too Many
Data in Loop.

现在需要将此类替换原有的GeneralForStatement,

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class OnlineTemplateEngine extends DefaultTemplateEngine
{
public Program createProgram(Resource resource, Reader reader, Map<Integer, String> textMap, String cr,
GroupTemplate gt)
{
Program program = super.createProgram(resource, reader, textMap, cr, gt);
modifyStatemetn(resource,program,gt);
return program;
}
private void modifyStatemetn(Resource resource,Program program,GroupTemplate gt){
Statement[] sts = program.metaData.statements;
StatementParser parser = new StatementParser(sts, gt, resource.getId());
parser.addListener(WhileStatement.class, new RestrictLoopNodeListener());
parser.addListener(GeneralForStatement.class, new RestrictLoopNodeListener());
parser.parse();
}
}

继承FastRuntimeEngine有所不同,因为改引擎会copy出一个脚本做分析优化,因此,俩个脚本都需要做修改

 1
2
3
4
5
6
7
8
9
10
11
12
13
public class OnlineTemplateEngine extends FastRuntimeEngine
{
public Program createProgram(Resource resource, Reader reader, Map<Integer, String> textMap, String cr,
GroupTemplate gt)
{
FilterProgram program = (FilterProgram)super.createProgram(resource, reader, textMap, cr, gt);
modifyStatemetn(resource,program,gt);
modifyStatemetn(resource,program.getCopy(),gt);
return program;
} }
  • StatementParser 是关键类,他允许对模板的Program进行解析,并替换其中的Statement。parser.addListener 方法接受俩个参数,第一个是需要找的类,第二个是执行的监听器。

  • 可以参考在线体验的源码:http://git.oschina.net/xiandafu/beetlonline/blob/master/src/org/bee/tl/online/OnlineTemplateEngine.java

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class RestrictLoopNodeListener implements Listener
{ @Override
public Object onEvent(Event e)
{
Stack stack = (Stack) e.getEventTaget();
Object o = stack.peek();
if (o instanceof GeneralForStatement)
{
GeneralForStatement gf = (GeneralForStatement) o;
RestrictForStatement rf = new RestrictForStatement(gf);
return rf;
} else
{
return null;
}
}

该监听器返回一个新的RestrictForStatement 类,用来替换来的GeneralForStatement。如果返回null,则不需替换。这通常发生在你仅仅通过修改该类的某些属性就可以的场景

完成这些代码后,在配置文件中申明使用新的引擎

1
ENGINE=org.bee.tl.online.OnlineTemplateEngine

这样就完成了模板引擎定制。

3.17. 直接运行Beetl脚本

Beetl模板本质上会转化为Beetl脚本来执行,这点跟jsp转为servlet来执行类似。GroupTemplate提供方法可以直接执行Beetl脚本

  • public Map runScript(String key, Map<String, Object> paras) throws ScriptEvalError

  • public Map runScript(String key, Map<String, Object> paras, Writer w) throws ScriptEvalError

  • public Map runScript(String key, Map<String, Object> paras, Writer w, ResourceLoader loader) throws ScriptEvalError

key为资源名,paras为脚本的全局变量,w可选参数,如果执行脚本有输出,则输出到w里,loader参数可选,如果指定,则使用此laoder加载脚本

执行脚本完毕后,返回到Map里的值可能包含如下:

  • 模板的顶级的临时变量,key为临时变量名

  • return 值将返回到map里 ,key为return

如下脚本(此时就不需要脚本定界符了)

1
2
3
4
var a = 1;
var b = date();
var c = '2';
return a+1;

调用runScript后,map里将返回key分别为a,b,c,return。 值分别为1,当前日期,字符串'2,以及3.

Beetl学习总结(3)——高级功能的更多相关文章

  1. MVC5 Entity Framework学习之Entity Framework高级功能(转)

    在之前的文章中,你已经学习了如何实现每个层次结构一个表继承.本节中你将学习使用Entity Framework Code First来开发ASP.NET web应用程序时可以利用的高级功能. 在本节中 ...

  2. MVC5 Entity Framework学习之Entity Framework高级功能

    在之前的文章中,你已经学习了怎样实现每一个层次结构一个表继承. 本节中你将学习使用Entity Framework Code First来开发ASP.NET web应用程序时能够利用的高级功能. 在本 ...

  3. Unity3D之Mecanim动画系统学习笔记(十一):高级功能应用

    动作游戏 还记得读书的时候熬夜打<波斯王子>的时光,我们的王子通过跳跃穿过墙壁的小洞.在高层建筑上进行攀爬和跳跃,还有在操作失误掉下高楼和触发必死机关后使用时之沙的时光倒流功能回归死亡之前 ...

  4. Hadoop学习笔记(7) ——高级编程

    Hadoop学习笔记(7) ——高级编程 从前面的学习中,我们了解到了MapReduce整个过程需要经过以下几个步骤: 1.输入(input):将输入数据分成一个个split,并将split进一步拆成 ...

  5. SLAM+语音机器人DIY系列:(二)ROS入门——10.在实际机器人上运行ROS高级功能预览

    摘要 ROS机器人操作系统在机器人应用领域很流行,依托代码开源和模块间协作等特性,给机器人开发者带来了很大的方便.我们的机器人“miiboo”中的大部分程序也采用ROS进行开发,所以本文就重点对ROS ...

  6. Jexus高级功能设置

    我们对服务器软件Jexus作了简单的介绍,同时我们也对Jexus的整体配置作了详细的讲解,介绍了Jexus的进程守护工具"jws.guard",相信各位读者对于Jexus应该已经有 ...

  7. Beetl学习总结(2)——基本用法

    2.1. 安装 如果使用maven,使用如下坐标 <dependency> <groupId>com.ibeetl</groupId> <artifactId ...

  8. matlab学习笔记9 高级绘图命令_2 图形的高级控制_视点控制和图形旋转_色图和颜色映像_光照和着色

    一起来学matlab-matlab学习笔记9 高级绘图命令_2 图形的高级控制_视点控制和图形旋转_色图和颜色映像_光照和着色 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考书籍 < ...

  9. matlab学习笔记9 高级绘图命令_1 图形对象_根对象,轴对象,用户控制对象,用户菜单对象

    一起来学matlab-matlab学习笔记9 高级绘图命令_1 图形对象_根对象,轴对象,用户控制对象,用户菜单对象 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考书籍 <matl ...

  10. Xen之初体验:XenMotion、 StorageMotion、Site Recovery、Power Management 各种新、高级功能免费

    Xenserver 的新版本6.2现在已经全面开源,省掉了原有的序列号,也能免费体验曾经标题中的付费高级功能. 安装镜像:http://downloadns.citrix.com.edgesuite. ...

随机推荐

  1. go语言笔记——包的概念本质上和java是一样的,通过大小写来区分private,fmt的Printf不就是嘛!

    示例 4.1 hello_world.go package main import "fmt" func main() { fmt.Println("hello, wor ...

  2. bzoj 3312 No Change

    题目大意: 到商场购物,他的钱包里有K个硬币 想按顺序买 N个物品,第i个物品需要花费c(i)块钱 在依次进行的购买N个物品的过程中,可以随时停下来付款,每次付款只用一个硬币 支付购买的内容是从上一次 ...

  3. POJ 3264 Balanced Lineup 区间最值

    POJ3264 比较裸的区间最值问题.用线段树或者ST表都可以.此处我们用ST表解决. ST表建表方法采用动态规划的方法, ST[I][J]表示数组从第I位到第 I+2^J-1 位的最值,用二分的思想 ...

  4. CSS小代码汇总整理

    /**实现斑马线的表格*/table.flexme tbody tr:nth-child(2n){background-color:#D6E7FC;} /*返回偶数序的子元素*/table.flexm ...

  5. 【WIP】Rails Client Side Document

    创建: 2017/09/15 更新: 2019/04/14 删除其他语言的表述 更新: 2017/10/14 标题加上[WIP]  引入JavaScrpit/CSS  manifesto  n. 货单 ...

  6. activiti安装-------安装插件

    对上面的放大

  7. 产生冠军(toposort)

    http://acm.hdu.edu.cn/showproblem.php?pid=2094 #include <stdio.h> #include <iostream> #i ...

  8. poj1200Crazy Search(hash)

    题目大意   将一个字符串分成长度为N的字串.且不同的字符不会超过NC个.问总共有多少个不同的子串. /* 字符串hash O(n)枚举起点 然后O(1)查询子串hash值 然后O(n)找不一样的个数 ...

  9. [Swift通天遁地]八、媒体与动画-(12)CoreText框架中的字体的FontMetrics布局信息

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...

  10. jmeter 3.x plugins 的使用

    JMeter Plugins 一直以来,JMeter Plugins为我们提供了很多高价值的JMeter插件,比如: 用于服务器性能监视的PerfMon Metrics Collector 用于建立压 ...