Beetl学习总结(3)——高级功能
2016-11-07 15:21
218 查看
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 |
配置文件第三部分是扩展部分,如方法,格式化函数等
1 2 3 4 5 6 | ##### 扩展 ############## ## 内置的方法 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 |
3.2. 自定义方法
3.2.1. 实现Function
1 2 3 4 5 6 | 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 ""; } |
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 | 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); %> |
1 2 3 | <% return date(); %> |
1 | hello time is ${now(),‘yyyy-MM-dd’} |
3.3. 自定义格式化函数
需要实现Format接口1 2 3 4 5 6 | 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"); } } |
也可以实现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 | public class DeleteTag extends Tag { @Override public void render() { // do nothing,just ignore body ctx.byteWriter.write("被删除了,付费可以看") } } |
1 2 3 4 5 6 | public class XianDeDantengTag extends Tag { @Override public void render() { doBodyRender(); } } |
1 2 3 4 5 6 | public class CompressTag extends Tag { @Override public void render() { BodyContent content = getBodyContent(); String content = content.getBody(); String zip = compress(conent); ctx.byteWriter.write(zip); } } |
tag类提供了如下属性和方法供使用
args 传入标签的参数
gt GroupTemplate
ctx Context
bw 当前的输出流
bs 标签体对应的语法树,不熟悉勿动
3.5. 自定义虚拟属性
可以为特定类注册一个虚拟属性,也可以为一些类注册虚拟属性public void registerVirtualAttributeClass(Class cls, VirtualClassAttribute virtual) 实现VirtualClassAttribute方法可以为特定类注册一个需要属性,如下代码:
1 2 3 4 5 6 | 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"; } } } }); |
public void registerVirtualAttributeEval(VirtualAttributeEval e) 为一些类注册需要属性,VirtualAttributeEval.isSupport方法将判断是否应用虚拟属性到此类
如下是虚拟属性类的定义
1 2 3 4 5 6 | 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 | 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 | 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); } |
1 2 3 4 5 6 | 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; } } |
1 2 3 4 5 6 | @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("/")); } } |
1 2 3 4 5 6 | 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); } } } |
1 2 3 4 5 6 | public abstract class Resource { /** * 打开一个新的Reader * * @return */ public abstract Reader openReader(); /** * 检测资源是否改变 * * @return */ public abstract boolean isModified(); |
3.8. 使用CompositeResourceLoader
组合加载器,可以包含多个已有的ResourceLoader,如下代码创建一个包含俩个文件和内存的ResourceLoader1 2 3 4 5 6 | 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"); |
1 2 3 | <% include("/xxx2.html"){} include("http:/xxx.html"){} %> |
1 2 3 | <% include("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 | public class ReThrowConsoleErrorHandler extends ConsoleErrorHandler { @Override public void processExcption(BeetlException ex, Writer writer) { super.processExcption(ex, writer); throw ex; } } |
type 一个简单的中文描述
errorCode 内部使用的错误类型标识
errorTokenText 错误发生的节点文本
errorTokenLine 错误行
msg 错误消息,有可能没有,因为有时候errorCode描述的已经很清楚了
cause 错误的root 异常,也可能没有。
BeetlException 也包含了一个关键信息就是 resourceId,即出错所在的模板文件
3.10. 自定义安全管理器
所有模板的本地调用都需要通过安全管理器校验,默认需要实现NativeSecurityManager 的public boolean permit(String resourceId, Class c, Object target, Stringmethod) 方法
如下是默认管理器的实现方法
1 2 3 4 5 6 | 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 正文 .......... <%}%> |
1 2 3 | <%include("/inc/header.html"){} %> this is content:${layoutContent} this is footer: |
全局变量总是能被布局用的页面所使用,如果布局页面需要临时变量,则需要显示的传入,如:
1 2 3 | <% var user= model.user; include("/inc/header.html",{title:'这是一个测试页面',user:user}){} %> |
继承布局:采用模板变量和include
1 2 3 4 5 6 | <% var jsPart = { %> web页面js部分 <%};%> <% var htmlPart = { %> web页面html部分 <%}; include("/inc/layout.html",{jsSection:jsPart,htmlSection:htmlPart}){} %> |
1 2 3 4 5 6 | <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 | PlaceholderST.output = new PlaceholderST.Output(){ @Override public void write(Context ctx, Object value) throws IOException { //定制输出 ctx.byteWriter.writeString("ok"+value!=null?value.toString:""); } }; |
3.16. 定制模板引擎
Beetl在线体验(http://ibeetl.com:8080/beetlonline/)面临一个挑战,允许用户输入任何脚本做练习或者分享代码。但又需要防止用户输入恶意的代码,如1 2 3 4 5 | <% for(var i=0;i<10000000;i++){ //其他代码 } %> |
1 2 3 4 5 6 | 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); } } |
Data in Loop.
现在需要将此类替换原有的GeneralForStatement,
1 2 3 4 5 6 | 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(); } } |
1 2 3 4 5 6 | 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; } } |
可以参考在线体验的源码:http://git.oschina.net/xiandafu/beetlonline/blob/master/src/org/bee/tl/online/OnlineTemplateEngine.java
1 2 3 4 5 6 | 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; } } |
完成这些代码后,在配置文件中申明使用新的引擎
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 | var a = 1; var b = date(); var c = '2'; return a+1; |
相关文章推荐
- Beetl学习总结(3)——高级功能
- 学习使用百度谷歌等高级搜索功能
- UNIX环境高级编程学习之第四章文件和目录-用C实现Shell中的"ls -l"功能
- JavaScript高级程序设计学习总结二(JavaScript复杂的变量与内存问题)
- 字串处理 expr在linux中是一个功能非常强大的命令。通过学习做一个小小的总结。
- c++高级---C_C++的union的学习笔记总结
- wpf学习笔记-数据绑定功能总结
- <<UNIX环境高级编程>>学习总结——第二章:UNIX标准化及实现
- 自己以前写的函数(总结一下,都是学习Unix高级编成练手的)
- [学习记录]屏蔽Activity, Dialog风格Activity, AlertDialog的Home键功能方法(总结)
- Linux bash总结(二) 高级部分(适合初学者学习和非初学者参考) (持续更新中...)
- UNIX环境高级编程学习之第六章系统数据文件和信息-修改第四章实现的Shell的“ls -l”功能
- 【SQL学习】——第四部分:DBMS扩展功能与SQL高级话题
- linux gcc及gdb常用功能学习总结
- JavaScript高级程序设计学习总结一(基本概念总结)
- Oracle 10g 统计信息自动收集功能(automatic statistics gathering)学习总结
- VBA实战技巧精粹001:关于高级筛选功能的学习及VBA实现
- USB学习总结3—USB gadget设备驱动实现(usb串口功能)
- JavaScript高级程序设计学习总结四(JavaScript引用类型二)
- <<UNIX环境高级编程>>学习总结——第一章:UNIX基础知识