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,请参考下一章

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

 123456789
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

 123456789
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接口

 123456789
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方法即可

 123456789
10
11
12

public class DeleteTag extends Tag
{@Overridepublic void render(){// do nothing,just ignore bodyctx.byteWriter.write("被删除了,付费可以看")}}

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

 123456789
10
11

public class XianDeDantengTag extends Tag
{@Overridepublic void render(){doBodyRender();}}

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

 123456789
10
11
12
13
14

public class CompressTag extends Tag
{@Overridepublic 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方法可以为特定类注册一个需要属性,如下代码:

 123456789
10
11
12
13
14
15
16
17
18
19
20
21
22

gt.registerVirtualAttributeClass(User.class, new VirtualClassAttribute() {@Overridepublic 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方法将判断是否应用虚拟属性到此类

如下是虚拟属性类的定义

 123456789
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)来完成;

 123456789
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类。如下:

 123456789
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

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

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

 123456789
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

@Overridepublic 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下的所有模板,并注册为方法

 123456789
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(通常数据库应该这么设计)来判断模板是否更改

 123456789
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

 123456789
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方法,打印异常后抛出

 123456789
10
11
12
13

public class ReThrowConsoleErrorHandler extends ConsoleErrorHandler
{@Overridepublic 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) 方法

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

 123456789
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

public class DefaultNativeSecurityManager implements NativeSecurityManager
{@Overridepublic 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

 123456789
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的值做检查等,不必使用格式化函数,可以直接对占位符输出进行定制,代码如下

 123456789
10

PlaceholderST.output = new PlaceholderST.Output(){@Overridepublic 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) ,我们需要改成如下样子:

 123456789
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 blocke.printStackTrace();}}}@Overridepublic void infer(InferContext inferCtx){super.infer(inferCtx);}}

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

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

 123456789
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出一个脚本做分析优化,因此,俩个脚本都需要做修改

 123456789
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

 123456789
10
11
12
13
14
15
16
17
18
19
20

class RestrictLoopNodeListener implements Listener
{@Overridepublic 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. beetl 获取html属性值,Beetl2.7.16中文文档(2)之高级功能

    3. 高级功能 3.1. 配置GroupTemplate Beetl建议通过配置文件配置GroupTemplate,主要考虑到IDE插件未来可能会支持Beetl模板,模板的属性,和函数等如果能通过配置 ...

  2. 0基础学习VR全景平台篇 第58篇:高级功能-AI人像合成

    功能位置示意 一.本功能将用在哪里? AI人像合成是蛙色VR商业级方案中,重要的裂变推广功能之一: 选取对应照片,通过后台生成实现个人与场景的合影留念,一键转发分享快速传播裂变. 观者者使用本功能时, ...

  3. 实现一个可管理、增发、兑换、冻结等高级功能的代币

    本文首发于深入浅出区块链社区 原文链接:实现一个可管理.增发.兑换.冻结等高级功能的代币 本文主要介绍代币高级功能的实现: 代币管理.代币增发.代币兑换.资产冻结.Gas自动补充. 写在前面 在上一篇 ...

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

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

  5. sql取整数_SQL高级功能

    1.窗口函数 窗口函数用于日常工作中,经常会遇到的需要在每组内排名的问题,比如: 排名问题:每个部门按业绩来排名 topN问题:找出每个部门排名前N的员工进行奖励 此时需要使用sql的高级功能窗口函数 ...

  6. 给代币添加高级功能-代币管理、增发、兑换、冻结等

    最新内容会更新在主站深入浅出区块链社区 原文链接:实现一个可管理.增发.兑换.冻结等高级功能的代币 本文主要介绍代币高级功能的实现: 代币管理.代币增发.代币兑换.资产冻结.Gas自动补充. 写在前面 ...

  7. 融云发布公告:五大高级功能将全面开放

    全球富媒体通讯云服务提供商--融云(公司全称:北京云中融信网络科技有限公司)在其官网发布了5大高级功能全面开放公告. 据该公告显示,为了更好的服务于客户,融云将于 2017 年 9 月 1 日起执行新 ...

  8. python与建筑行业_Python+AI+CC,这些建模“高级功能”,90%的人不知道

    1. Python编写脚本,实现CC自动化建模 Python脚本实现CC自动化建模 MasterKernel是一个以编程方式访问ContextCapture Master所有功能的Python模块,此 ...

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

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

  10. RabbitMQ学习笔记(高级篇)

    RabbitMQ学习笔记(高级篇) 文章目录 RabbitMQ学习笔记(高级篇) RabbitMQ的高级特性 消息的可靠投递 生产者确认 -- confirm确认模式 生产者确认 -- return确 ...

最新文章

  1. H5 画布解决跨域问题,画布保存为图片显示在页面上
  2. 04 - Object-C协议与代理设计
  3. AS3中的序列化与反序列化
  4. python 编程入门-python编程入门(第3版)
  5. Spring核心技术(七)——Spring容器的扩展
  6. 字符串匹配算法(二):BM(BoyerMoore)算法、坏字符规则,好后缀规则
  7. 子网掩码及网络号路由表的匹配方法
  8. 添加图标_win10系统轻松添加显示桌面图标的操作方法
  9. 【数据结构与算法】双链表的Java实现
  10. Python基础——import(导入模块)
  11. img标签过滤加fs模块实现图片文件缓存
  12. C++ list 基础用法合集;list 增减元素 list删除某一个元素 遍历list
  13. 不能使用sizeof计算的表达式
  14. 使用LaTeX给PDF加背景
  15. Git如何统计代码行数
  16. Unity简单操作:Unity资源商店 Asset store下载文件夹的位置
  17. 学习dicom没什么窍门
  18. java课设超市收银系统_基于jsp的超市收银系统-JavaEE实现超市收银系统 - java项目源码...
  19. 2020-12-12
  20. c语言课程设计报告之迷宫,C语言课程设计-迷宫游戏

热门文章

  1. mysql5.6 排序失效_mysql 使用union(all) + order by 导致排序失效
  2. mongodb spring 超时时间_mongoDB(spring 部分配置)
  3. dos 退出mysql_【转】MySQL 一闪退出解决_MySQL
  4. python画图为什么运行不出来_PyCharm中Matplotlib绘图不能显示UI效果的问题解决
  5. c语言函数修改指针本身,C语言函数内部改变指针本身
  6. 模拟黑洞图像_黑洞“真容”今晚将被公布,先马约你一起见证~
  7. 输入法快捷键_[秒杀必备]搜狗输入法自定义短语快捷键设置
  8. c++ 字符串合并_Python基础字符串处理
  9. seo按天扣费系统_网站seo优化多少钱,SEO快速排名按天扣费怎么样
  10. oracle下tmp后缀文件怎么打开,什么是TMP文件,怎么打开?(图文)