Beetl目前版本是2.8.5,相对于其余java模板引擎,具备功能齐全,语法直观,性能超高,以及编写的模板容易维护等特色。使得开发和维护模板有很好的体验。是新一代的模板引擎。总得来讲,它的特性以下:javascript
关于性能
在使用FastRuntimeEngine状况下,经过与主流模板引擎Freemarker,Vecloity以及JSP对比,Beetl6倍于Freemarker,2倍于JSP。这是由于宏观上,经过了优化的渲染引擎,IO的二进制输出,字节码属性访问加强,微观上,经过一维数组保存上下文Context,静态文本合并处理,经过重复使用字节数组来防止java频繁的建立和销毁数组,还使用模板缓存,运行时优化等方法。详情参考附录css
独特功能
Beetl有些功能是发展了10多年的模板引擎所不具有的,这些功能很是利于模板的开发和维护,以下html
- 自定义占位符和控制语句起始符号,这有利于减少模板语法对模板的倾入性,好比在html模板中,若是定义控制语句符号是
<!--:
和-->
,那么,大部分模板文件都能经过浏览器打开。有的使用者仅仅采用了单个符号@
(或者单个符号“~
”)以及回车换号做为控制语句起始符号,这又能提升开发效率- 可单独测试的模板。无需真正的控制层和模型层,Beetl的模板就能够单独开发和测试
- 同时支持较为松散的MVC和严格的MVC,若是在模板语言里嵌入计算表达式,复杂条件表达式,以及函数调用有干涉业务逻辑嫌疑,你能够禁止使用这些语法。
- 强大的安全输出,经过安全输出符号!,能在模板变量,变量属性引用,for循环,占位符输出,try-catch中等各个地方提供安全输出,保证渲染正常。
- 模板变量:运行将模板的某一部分输出像js那样赋值给一个变量,稍后再处理。利用模板变量能完成很是复杂的页面布局(简单的布局可以使用include,layout标签函数)
- 类型推测,能在运行的时候推测模板变量类型,从而优化性能,也能够经过注解的方法显示的说明模板变量属性(这是非必须的,但有助于IDE自动提示功能)
- 可插拔的设计,错误信息提示,模板引擎缓存机制,模板资源管理,本地调用的安全管理器,严格MVC限制,模板引擎自己都有默认的实现,但又彻底能够自定义以适合特定需求
- 加强的语法,如for-elsefor, select-case,安全输出符号!,省略的三元表达式 等,这些语法特别适合模板开发
- 局部渲染技术,结合如今js的ajax技术。
- 性能超高,具备最快的模板解释引擎,同时,又有较低的CPU消耗。5-6倍于国内使用的Freemaker。适合各种模板应用,如代码生成工具,CMS系统,普通网站,超高访问量的门户系统,和富客户端JS框架整合的后台管理应用
小白如何开始
- 须要通读基本用法,大部分都是讲解语法,而语法跟js很接近,因此能够快速预览,但Beetl是针对模板设计, 因此像安全输出,标签和html标签,全局变量,临时变量和共享变量,布局技术,以及直接调用java代码等还须要认真读一遍。
- 若是从事web开发,还须要阅读web集成里的第一节“web提供的全局变量”,若是web里还使用ajax技术,能够阅读“整合ajax的局部渲染技术”。
- 包含有spring,jfinal,jodd,struts 等demo能够做为参考学习用https://git.oschina.net/xiandafu 任何问题,均可以在ibeetl.com 社区上提问。目前答复率是100%,提问须要详细说明本身的指望,出错信息,附上代码或者图片
联系做者
做者:闲.大赋 (李家智)等(参考附录查看代码贡献者)前端
QQ技术交流群:219324263(满) 636321496java
邮件:xiandafu@126.comgit
Beetl社区:bbs.ibeetl.com程序员
源码主页:https://github.com/javamonkey/beetl2.0github
在线体验和代码分享 http://ibeetl.com/beetlonline/web
若是使用maven,请使用以下坐标ajax
<dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl</artifactId> <version>2.8.5</version> </dependency>
若是非maven工程,直接下载http://git.oschina.net/xiandafu/beetl2.0/attach_files
StringTemplateResourceLoader resourceLoader = new StringTemplateResourceLoader(); Configuration cfg = Configuration.defaultConfiguration(); GroupTemplate gt = new GroupTemplate(resourceLoader, cfg); Template t = gt.getTemplate("hello,${name}"); t.binding("name", "beetl"); String str = t.render(); System.out.println(str);
Beetl的核心是GroupTemplate,建立GroupTemplate须要俩个参数,一个是模板资源加载器,一个是配置类,模板资源加载器Beetl内置了6种,分别是
代码第5行将变量name传入模板里,其值是“Beetl”。 代码第6行是渲染模板,获得输出,template提供了多种得到渲染输出的方法,以下
- 关于如何使用模板资源加载器,请参考下一节
- 如何对模板进行配置,请参考下一节
- 若是不想写代码直接体验Beetl,可使用http://ibeetl.com/beetlonline/
Beetl提供不但功能齐全,并且还有不少独特功能,经过简单的配置文件,就能够定义众多的功能,默认状况下,Configuration类老是会先加载默认的配置文件(位于/org/beetl/core/beetl-default.properties,做为新手,一般只须要关注3,4,5,6行定界符的配置,以及12行模板字符集的配置就能够了,其余配置会在后面章节陆续提到,同时,对于Spring等框架,有些配置将会被这些框架的配置覆盖,须要参考后面章节)下,其内容片段以下:
#默认配置 ENGINE=org.beetl.core.engine.DefaultTemplateEngine DELIMITER_PLACEHOLDER_START=${ DELIMITER_PLACEHOLDER_END=} DELIMITER_STATEMENT_START=<% DELIMITER_STATEMENT_END=%> DIRECT_BYTE_OUTPUT = FALSE HTML_TAG_SUPPORT = true HTML_TAG_FLAG = # HTML_TAG_BINDING_ATTRIBUTE = var NATIVE_CALL = TRUE TEMPLATE_CHARSET = UTF-8 ERROR_HANDLER = org.beetl.core.ConsoleErrorHandler NATIVE_SECUARTY_MANAGER= org.beetl.core.DefaultNativeSecurityManager MVC_STRICT = FALSE #资源配置,resource后的属性只限于特定ResourceLoader RESOURCE_LOADER=org.beetl.core.resource.ClasspathResourceLoader #classpath 根路径 RESOURCE.root= / #是否检测文件变化,开发用true合适,但线上要改成false RESOURCE.autoCheck= true #自定义脚本方法文件的Root目录和后缀 RESOURCE.functionRoot = functions RESOURCE.functionSuffix = html #自定义标签文件Root目录和后缀 RESOURCE.tagRoot = htmltag RESOURCE.tagSuffix = tag ##### 扩展 ############## ## 内置的方法 FN.date = org.beetl.ext.fn.DateFunction ...... ##内置的功能包 FNP.strutil = org.beetl.ext.fn.StringUtil ...... ##内置的默认格式化函数 FTC.java.util.Date = org.beetl.ext.format.DateFormat ..... ## 标签类 TAG.include= org.beetl.ext.tag.IncludeTag
这配置文件总体说明了Beetl提供的功能
第2行配置引擎实现类,默认便可.
第3,4行指定了占位符号,默认是${
}
,也能够指定为其余占位符。
第5,6行指定了语句的定界符号,默认是<%
%>
,也能够指定为其余定界符号
第7行指定IO输出模式,默认是FALSE,即一般的字符输出,在考虑高性能状况下,能够设置成true
。详细请参考高级用法
第8,9行指定了支持HTML标签,且符号为#,默认配置下,模板引擎识别<#tag ></#tag>
这样的相似html标签,并能调用相应的标签函数或者模板文件。你也能够指定别的符号,如bg: 则识别<bg:
第10行 指定若是标签属性有var
,则认为是须要绑定变量给模板的标签函数
第11行指定容许本地Class直接调用
第12行指定模板字符集是UTF-8
第13行指定异常的解析类,默认是ConsoleErrorHandler,他将在render发生异常的时候在后台打印出错误信息(System.out
)。
第14行指定了本地Class调用的安全策略
第15行配置了是否进行严格MVC,一般状况下,此处设置为false.
第18行指定了默认使用的模板资源加载器,注意,在beetl与其余MVC框架集成的时候,模板加载器不必定根据这个配置,好比spring,他的RESOURCE_LOADER以spring的配置为准
第20到22行配置了模板资源加载器的一些属性,如设置根路径为/,即Classpath的顶级路径,而且老是检测模板是否更改
第23行配置了自定义的方法所在的目录以及文件名后缀。beetl既支持经过java类定义方法,也支持经过模板文件来定义方法
第26行配置了自定义的html标签所在的目录以及文件名后缀。beetl既支持经过java类定义标签,也支持经过模板文件来定义标签
第31行注册了一个date
方法,其实现类是org.beetl.ext.fn.DateFunction
第34行注册了一个方法包strutil
,其实现类org.beetl.ext.fn.StringUtil
,此类的每一个public
方法都将注册为beetl的方法
第37行注册了一个日期格式化函数
第40行注册了一个include
标签函数
模板开发者能够建立一个beetl.properties的配置文件,此时,该配置文件将覆盖默认的配置文件属性,好比,你的定界符考虑是
<!--:
和-->
,则在beetl.properties加入一行便可,并将此配置文件放入Classpath根目录下便可。 Configuration.defaultConfiguration()老是先加载系统默认的,而后再加载Beetl.properties的配置属性,若是有重复,用后者代替前者的配置#自定义配置 DELIMITER_STATEMENT_START=<!--: DELIMITER_STATEMENT_END=-->
2.4.0 新功能:beetl 支持经过模板本生来完成函数,即模板函数,或者经过模板来实现HTML标签(而不用写java代码),能够beetl.properties为这种应用设置的不一样的语句定界符来跟常规模板作区分,以下
FUNCTION_TAG_LIMITER=<% ; %>
分号分割开,若是配置文件没有FUNCTION_TAG_LIMITER=,则模板函数,html标签使用同DELIMITER_STATEMENT_START,DELIMITER_STATEMENT_END
资源加载器是根据String值获取Resource实例的工场类,同时资源加载器还要负责响应模板引擎询问模板是否变化的调用。对于新手来讲,无需考虑模板资源加载器如何实现,只须要根据本身场景选择系统提供的三类模板资源加载器便可
在建立GroupTemplate过程当中,若是传入的是StringTemplateResourceLoader,则容许经过调用gt.getTemplate(String template)来获取模板实例对象,如2.1所示
更一般状况下,模板资源是以文件形式管理的,集中放在某一个文件目录下(如webapp的模板根目录就多是WEB-INF/template里),所以,可使用FileResourceLoader来加载模板实例,以下代码:
String root = System.getProperty("user.dir")+File.separator+"template"; FileResourceLoader resourceLoader = new FileResourceLoader(root,"utf-8"); Configuration cfg = Configuration.defaultConfiguration(); GroupTemplate gt = new GroupTemplate(resourceLoader, cfg); Template t = gt.getTemplate("/s01/hello.txt"); String str = t.render(); System.out.println(str);
第1行代码指定了模板根目录,即位于项目工程下的template目录 第2行构造了一个资源加载器,并指定字符集为UTF-8 (也可不指定,由于配置文件默认就是UTF-8); 第5行经过模板的相对路径/s01/hello.txt来加载模板
还有种常状况下,模板资源是打包到jar文件或者同Class放在一块儿,所以,可使用ClasspathResourceLoader来加载模板实例,以下代码:
ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader("org/beetl/sample/s01/"); Configuration cfg = Configuration.defaultConfiguration(); GroupTemplate gt = new GroupTemplate(resourceLoader, cfg); Template t = gt.getTemplate("/hello.txt"); String str = t.render(); System.out.println(str);
第1行代码指定了模板根目录,即搜索模板的时候从根目录开始,若是new ClasspathResourceLoader("template/"),则表示搜索template下的模板。此处用空构造函数,表示搜索路径是根路径,且字符集默认字符集UTF-8.
第4行经过模板的相对路径org/beetl/sample/s01/hello.txt来加载模板
WebAppResourceLoader 是用于web应用的资源模板加载器,默认根路径是WebRoot目录。也能够经过制定root属性来设置相对于WebRoot的的模板根路径,从安全角考虑,建议放到WEB-INF目录下
以下是Jfinal集成 里初始化GroupTemplate的方法
Configuration cfg = Configuration.defaultConfiguration(); WebAppResourceLoader resourceLoader = new WebAppResourceLoader(); groupTemplate = new GroupTemplate(resourceLoader, cfg);
WebAppResourceLoader 假定 beetl.jar 是位于 WEB-INF/lib 目录下,所以,能够经过WebAppResourceLoader类的路径来推断出WebRoot路径从而指定模板根路径。全部线上环境通常都是如此,若是是开发环境或者其余环境不符合此假设,你须要调用resourceLoader.setRoot() 来指定模板更路径
有时候模板可能来自文件系统不一样目录,或者模板一部分来自某个文件系统,另一部分来自数据库,还有的状况模板多是加密混淆的模板,此时须要自定义资源加载,继承ResouceLoader才能实现模板功能,这部分请参考高级部分
Beetl模板语言相似JS语言和习俗,只须要将Beetl语言放入定界符号里便可,如默认的是<% %> ,占位符用于静态文本里嵌入占位符用于输出,以下是正确例子
<% var a = 2; var b = 3; var result = a+b; %> hello 2+3=${result}
千万不要在定界符里使用占位符号,由于占位符仅仅嵌在静态文本里,以下例子是错误例子
<%
var a = "hi"; var c = ${a}+"beetl"; //应该是var c = a+"beetl" %>
每次有人问我如上例子为啥不能运行的时候,我老是有点憎恶velocity 带来的这种非人性语法
定界符和占位符 一般还有别的选择,以下定界符
占位符--#{ }-##
你也能够与团队达成一致意见来选择团队喜好的定界符号和占位符号。
定界符号里是表达式,若是表达式跟定界符或者占位符有冲突,能够在用 “\” 符号,如
@for(user in users){ email is ${user.name}\@163.com @} ${[1,2,3]} //输出一个json列表 ${ {key:1,value:2 \} } //输出一个json map,} 须要加上\
Beetl语法相似js语法,因此注释上也同js同样: 单行注释采用//
多行注视采用/**/
<%
/*此处是一个定义变量*/ var a = 3; //定义一个变量. /* 如下内容都将被注释 %> <% */ %>
第2行是一个多行注释
第3行是一个单行注释
第5行到第8行采用的是多行注释,所以里面有内容也是注释,模板将不予处理
在模板中定义的变量成为临时变量,这相似js中采用var 定义的变量,以下例子
<% var a = 3; var b = 3,c = "abc",d=true,e=null; var f = [1,2,3]; var g = {key1:a,key2:c}; var i = a+b; %>
全局变量是经过template.binding传入的变量,这些变量能在模板的任何一个地方,包括子模板都能访问到。如java代码里
template.binding("list",service.getUserList()); //在模板里 <% for(user in list){ %> hello,${user.name}; <% } %>
自从2.8.0版本后,有一个特殊的变量成为root变量,当模板找不到变量的时候,会寻找root变量的属性来做为变量的值,这个root变量必须绑定为"_root"
template.binding("_root",new User()); //在模板里 ${name} ${wife.name}
这里name 和 wife都是User对象的属性
共享变量指在全部模板中均可以引用的变量,可经过groupTemplate.setSharedVars(Map<String, Object> sharedVars)传入变量,这些变量能用在 全部模板 的任何一个地方
//..... GroupTemplate gt = new GroupTemplate(resourceLoader, cfg); Map<String,Object> shared = new HashMap<String,Object>(); shared.put("name", "beetl"); gt.setSharedVars(shared); Template t = gt.getTemplate("/org/beetl/sample/s0208/t1.txt"); String str = t.render(); System.out.println(str); t = gt.getTemplate("/org/beetl/sample/s0208/t2.txt"); str = t.render(); System.out.println(str);
//t1.txt
hi,${name} //t2.txt hello,${name}
模板变量是一种特殊的变量,便可以将模板中任何一段的输出赋值到该变量,并容许稍后在其余地方使用,以下代码
<% var content = { var c = "1234"; print(c); %> 模板其余内容: <% }; %>
第2行定义了一个模板变量content = { …} ; 此变量跟临时变量同样,能够在其余地方使用,最多见的用法是用于复杂的布局。请参考高级用法布局
属性引用是模板中的重要一部分,beetl支持属性同javascript的支持方式同样,以下
template.binding("list",service.getUserList()); template.binding("pageMap",service.getPage()); //在模板里 总共 ${list.~size} <% for(user in list){ %> hello,${user.name}; <% } %> 当前页${pageMap['page']},总共${pageMap["total"]}
Beetl2.7.0 开始支持对象赋值,如:
<% var user = .... user.name="joelli"; user.friends[0] = getNewUser(); user.map["name"] = "joelli"; %>
Beetl支持相似javascript的算术表达式和条件表达式,如+ - * / % 以及(),以及自增++,自减--
<%
var a = 1; var b = "hi"; var c = a++; var d = a+100.232; var e = (d+12)*a; var f = 122228833330322.1112h %>
Beetl里定义的临时变量类型默认对应的java类型是Int型或者double类型,对于模板经常使用状况,已经够了.若是须要定义长精度类型(对应java的BigDecimal),则须要在数字末尾加上h以表示这是长精度BigDecimal,其后的计算和输出以及逻辑表达式都将按照长精度类型来考虑。
Beetl支持相似Javascript,java的条件表达式 如>,\<,==,!=,>= , \<= 以及 !, 还有&&和 || ,还有三元表达式等,以下例子
<%
var a = 1; var b="good"; var c = null; if(a!=1&&b=="good"&&c==null){ ...... } %>
三元表达式若是只考虑true条件对应的值的话,能够作简化,以下俩行效果是同样的。
<%
var a = 1 ;
%> ${a==1?"ok":''} ${a==1?"ok"}
Beetl支持丰富的循环方式,如for-in,for(exp;exp;exp),以及while循环,以及循环控制语句break;continue; 另外,若是没有进入for循环体,还能够执行elsefor指定的语句。
for-in循环支持遍历集合对象,对于List和数组来讲以及Iterator,对象就是集合对象,对于Map来讲,对象就是Map.entry,以下俩个例子
<%
for(user in userList){ print(userLP.index); print(user.name); } %>
第三行代码userLP是Beetl隐含定义的变量,能在循环体内使用。其命名规范是item名称后加上LP,他提供了当前循环的信息,如
如何记住后缀是LP,有俩个诀窍,英语棒的是Loop的缩写,拼音好的是老婆的拼音缩写,这可让程序员每次写到这的时候都会想一想老婆(无论有没有,哈哈)
以下是Map使用例子
<%
for(entry in map){ var key = entry.key; var value = entry.value; print(value.name); } %>
对于渲染逻辑更为常见的是经典的for循环语句,以下例子
<%
var a = [1,2,3]; for(var i=0;i<a.~size;i++){ print(a[i]); } %>
对于渲染逻辑一样常见的有的while循环语句,以下例子
<%
var i = 0; while(i<5){ print(i); i++; } %>
不一样于一般程序语言,若是没有进入循环体,则不需额外的处理,模板渲染逻辑更常见状况是若是没有进入循环体,还须要作点什么,所以,对于for循环来讲,还有elsefor 用来表达若是循环体没有进入,则执行elsefor 后的语句
<%
var list = []; for(item in list){ }elsefor{ print("未有记录"); } %>
同js同样,支持if else,以下例子
<%
var a =true; var b = 1; if(a&&b==1){ }else if(a){ }else{ } %>
同js同样,支持switch-case,以下例子
<%
var b = 1; switch(b){ case 0: print("it's 0"); break; case 1: print("it's 1"); break; default: print("error"); } %>
switch变量能够支持任何类型,而不像js那样只能是整形
select-case 是switch case的加强版。他容许case 里有逻辑表达式,同时,也不须要每一个case都break一下,默认遇到合乎条件的case执行后就退出。
<%
var b = 1; select(b){ case 0,1: print("it's small int"); case 2,3: print("it's big int"); default: print("error"); } %>
select 后也不须要一个变量,这样case 后的逻辑表达式将决定执行哪一个case.其格式是
<%
select { case boolExp,orBoolExp2: doSomething(); } %>
<%
var b = 1; select{ case b<1,b>10: print("it's out of range"); break; case b==1: print("it's 1"); break; default: print("error"); } %>
一般模板渲染逻辑不多用到try-catch 但考虑到渲染逻辑复杂性,以及模板也有不可控的地方,因此提供try catch,在渲染失败的时候仍然能保证输出正常
<%
try{ callOtherSystemView() }catch(error){ print("暂时无数据"); } %>
error表明了一个异常,你能够经过error.message 来获取可能的错误信息
也能够省略catch部分,这样出现异常,不作任何操做
虚拟属性也是对象的属性,是虚拟的,非模型对象的真实属性,这样的好处是当模板须要额外的用于显示的属性的时候但又不想更改模型,即可以采用这种办法 如beetl内置的虚拟属性.~size 针对了数组以及集合类型。
${user.gender} ${user.~genderShowName}
~genderShowName 是虚拟属性,其内部实现根据boolean变量gender来显示性别
如何完成虚拟属性,请参考高级用法
Beetl内置了少许实用函数,能够在Beetl任何地方调用。以下例子是调用date 函数,不传参数状况下,返回当前日期
<%
var date = date(); var len = strutil.length("cbd"); println("len="+len); %>
注意函数名支持namespace方式,所以代码第3行调用的函数是strutil.length
定义beetl的方法很是容易,有三种方法
- 实现Function类的call方法,并添加到配置文件里,或者显示的经过代码注册registerFunction(name,yourFunction)
- 能够直接调用registerFunctionPackage(namespace,yourJavaObject),这时候yourJavaObject里的全部public方法都将注册为Beetl方法,方法名是namespace+"."+方法名
- 能够直接写模板文件而且以html做为后缀,放到root/functions目录下,这样此模板文件自动注册为一个函数,其函数名是该模板文件名。
详情请参考高级用法
Beetl内置函数请参考附录,如下列出了经常使用的函数
安全输出是任何一个模板引擎必须重视的问题,不然,将极大困扰模板开发者。Beetl中,若是要输出的模板变量为null,则beetl将不作输出,这点不一样于JSP,JSP输出null,也不一样于Freemarker,若是没有用!,它会报错.
模板中还有俩种状况会致使模板输出异常
针对前俩种状况,能够在变量引用后加上!以提醒beetl这是一个安全输出的变量。
如${user.wife.name! },即便user不存在,或者user为null,或者user.wife为null,或者user.wife.name为null beetl都不将输出
能够在!后增长一个常量(字符串,数字类型等),或者另一个变量,方法,本地调用,做为默认输出,譬如:
${user.wife.name!”单身”},若是user为null,或者user.wife为null,或者user.wife.name为null,输出”单身”
譬如
${user.birthday!@System.constants.DefaultBir}, 表示若是user为null,或者user. birthday为null,输出System.constants.DefaultBir
还有一种状况不多发生,但也有可能,输出模板变量发生的任何异常,如变量内部抛出的一个异常
这须要使用格式${!(变量)},这样,在变量引用发生任何异常状况下,都不做输出,譬如
${!(user.name)},,beetl将会调用user.getName()方法,若是发生异常,beetl将会忽略此异常,继续渲染
值得注意的是,在变量后加上!不只仅能够应用于占位符输出(但主要是应用于占位符输出),也能够用于表达式中,如:
<%
var k = user.name!'N/A'+user.age!;
%> <% ${k} %>
若是user为null,则k值将为N/A
在有些模板里,可能整个模板都须要安全输出,也可能模板的部分须要安全输出,使用者没必要为每个表达式使用!,可使用beetl的安全指示符号来完成安全输出 如:
<%
DIRECTIVE SAFE_OUTPUT_OPEN;
%> ${user.wife.name} 模板其余内容,均能安全输出…… <% //关闭安全输出。 DIRECTIVE SAFE_OUTPUT_CLOSE; %>
Beetl不建议每个页面都使用DIRECTIVE SAFE_OUTPUT_OPEN,这样,若是真有不指望的错误,不容易及时发现,其次,安全输出意味着beetl会有额外的代码检测值是否存在或者是否为null,性能会略差点。因此建议及时关闭安全输出(这不是必须的,但页面全部地方是安全输出,可能不容易发现错误)
在for-in 循环中 ,也能够为集合变量增长安全输出指示符号,这样,若是集合变量为null,也能够不进入循环体,如:
<%
var list = null; for(item in list!){ }elsefor{ print("no data"); } %>
<%
if(has(flag)){ print("flag变量存在,能够访问") } %>
若是须要判断变量是否存在,若是存在,还有其余判断条件,一般都这么写
<%
if(has(flag)&&flag==0){ //code } %>
若是flag存在,并且值是0,都将执行if语句
可是,有更为简便的方法是直接用安全输出,如
<%
if(flag!0==0){ //code } %>
flag!0 取值是这样的,若是flag不存在,则为0,若是存在,则取值flag的值,相似三元表达式 if((has(flag)?flag:0)==0)
安全输出表达式能够包括
几乎全部的模板语言都支持格式化,Beetl也不列外,以下例子Beetl提供的内置日期格式
<% var date = date(); %> Today is ${date,dateFormat="yyyy-MM-dd"}. Today is ${date,dateFormat} salary is ${salary,numberFormat="##.##"}
格式化函数只须要一个字符串做为参数放在=号后面,若是没有为格式化函数输入参数,则使用默认值,dateFormat格式化函数默认值是local
Beetl也容许为指定的java class设定格式化函数,譬如已经内置了对java.util.Date,java.sql.Date 设置了了格式化函数,所以上面的例子能够简化为
${date,“yyyy-MM-dd”}
Beetl针对日期和数字类型提供的默认的格式化函数,在org/beetl/core/beetl-default.properties里,注册了
##内置的格式化函数 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 FTC.java.sql.Time = org.beetl.ext.format.DateFormat FTC.java.sql.Timestamp = org.beetl.ext.format.DateFormat FTC.java.lang.Short = org.beetl.ext.format.NumberFormat FTC.java.lang.Long = org.beetl.ext.format.NumberFormat FTC.java.lang.Integer = org.beetl.ext.format.NumberFormat FTC.java.lang.Float = org.beetl.ext.format.NumberFormat FTC.java.lang.Double = org.beetl.ext.format.NumberFormat FTC.java.math.BigInteger = org.beetl.ext.format.NumberFormat FTC.java.math.BigDecimal = org.beetl.ext.format.NumberFormat FTC.java.util.concurrent.atomic.AtomicLong = org.beetl.ext.format.NumberFormat FTC.java.util.concurrent.atomic.AtomicInteger = org.beetl.ext.format.NumberFormat
所谓标签函数,即容许处理模板文件里的一块内容,功能等于同jsp tag。如Beetl内置的layout标签
index.html
<%
layout("/inc/layout.html",{title:'主题'}){ %> Hello,this is main part <% } %>
layout.html
title is ${title} body content ${layoutContent} footer
第1行变量title来自于layout标签函数的参数
第2行layoutContent 是layout标签体{}渲染后的结果
关于layout标签,参考高级主题布局
Beetl内置了另一个标签是include,容许 include 另一个模板文件
<%
include("/inc/header.html"){} %>
在标签中,{} 内容将依据标签的实现而执行,layout标签将执行{}中的内容,而include标签则忽略标签体内容。
关于如何实现标签函数,请参考高级主题,以下是一个简单的的标签函数:
public class CompressTag extends Tag{ @Override public void render(){ BodyContent content = getBodyContent(); String content = content.getBody(); String zip = compress(conent); ctx.byteWriter.write(zip); } }
Beetl 也支持HTML tag形式的标签, 区分beetl的html tag 与 标准html tag。如设定HTML_TAG_FLAG=#,则以下html tag将被beetl解析
<#footer style=”simple”/> <#richeditor id=”rid” path="${ctxPath}/upload" name=”rname” maxlength=”${maxlength}”> ${html} …其余模板内容 </#richdeitor> <#html:input id=’aaaa’ />
如对于标签footer,Beetl默认会寻找WebRoot/htmltag/footer.tag(能够经过配置文件修改路径和后缀) ,内容以下:
<% if(style==’simple’){ %> 请联系我 ${session.user.name} <% }else{ %> 请联系我 ${session.user.name},phone:${session.user.phone} <% } %>
以下还包含了自定义html标签一些规则
若是采用模板来写html标签功能不够强大,beetl支持写标签函数(参考上一节)来实现html标签,标签函数args[0]表示标签名,这一般没有什么用处,args[1] 则是标签的属性,参数是个map,key是html tag的属性,value是其属性值,以下用java完成的html 标签用于输出属性值
public class SimpleHtmlTag extends Tag{ @Override public void render(){ String tagName = (String) this.args[0]; Map attrs = (Map) args[1]; String value = (String) attrs.get("attr"); try{ this.ctx.byteWriter.writeString(value); }catch (IOException e){ } } }
若是注册gt.registerTag("simpleTag", SimpleHtmlTag.class); 则以下模板输出了attr属性值abc
<#simpleTag attr="abc"></#simpleTag>
HTML_TAG_FLAG默认为#用来区别是不是beetl的html tag,你也能够设置成其余符号,好比 "my:",这样,\my:table\\</my:table> 实际上是一个指向table.tag的标签实现
对于html标签(参考上一节),Beetl还 支持将标签实现类(java代码)里的对象做为临时变量,被标签体引用。此时须要实现GeneralVarTagBinding (此类是Tag的子类) 该类提供另外3个方法 - void binds(Object… array) 子类在render方法里调用此类以实现变量绑定,绑定顺序同在模板中声明的顺序 - void bind(String name, Object value) 子类在render方法里调用此类以实现变量绑定,name是模板中声明的变量名,用此方法绑定不如binds更灵活,再也不推荐 - Object getAttributeValue 得到标签的属性 - Map getAttributes 得到标签的全部属性
public class TagSample extends GeneralVarTagBinding{ @Override public void render(){ int limit = Integer.parseInt((String) this.getAttributeValue("limit")); for (int i = 0; i < limit; i++){ this.binds(i) this.doBodyRender(); } } } //在某处注册一下标签TagSample //gt.registerTag("tag", TagSample.class);
如上例子,render方法将循环渲染标签体limit次,且每次都将value赋值为i。咱们再看看模板如何写的
<#tag limit="3";value> ${value} </#tag>
相似于常规html标签,须要在标签的最后的属性定义后面加上分号 ";" 此分号表示这个是一个须要在标签运行时须要绑定变量的标签。后跟上要绑定的变量列表,如上例只绑定了一个value变量,若是须要绑定多个变量,则用逗号分开,如var1,var2 上。若是后面没有变量列表,只有分号,则默认绑定到标签名同名的变量上. 若是标签有namesapce,则默认绑定订的变量名不包含namespace
注意,因为标签使用由于太长可能换行或者是文本格式化致使换行,目前beetl只容许在属性之间换行,不然,将报标签解析错误。
默认状况下,若是标签属性出现了var(能够经过配置文件改为其余属性名),也认为是绑定变量的标签,如上面的例子也能够这么写
<#tag limit="3" var="value"> ${value} </#tag>
var属性的值能够是个以逗号分开的变量名列表,如var="total,customer,index"
能够经过符号@来代表后面表达式调用是java风格,能够调用对象的方法,属性
${@user.getMaxFriend(“lucy”)}
${@user.maxFriend[0].getName()} ${@com.xxxx.constants.Order.getMaxNum()} ${@com.xxxx.User$Gender.MAN} <% var max = @com.xxxx.constants.Order.MAX_NUM; var c =1; var d = @user.getAge(c); %>
能够调用instance的public方法和属性,也能够调用静态类的属性和方法 ,须要加一个 @指示此调用是直接调用class,其后的表达式是java风格的。
- GroupTemplate能够配置为不容许直接调用Class,具体请参考配置文件.
- 也能够经过安全管理器配置到底哪些类Beetl不容许调用,具体请参考高级用法。默认状况,java.lang.Runtime,和 java.lang.Process不容许在模板里调用。你本身的安全管理器也能够配置为不能直接访问DAO类(避免了之前jsp能够访问任意代码带来的危害)
- 请按照java规范写类名和方法名,属性名。这样便于beetl识别到底调用的是哪一个类,哪一个方法。不然会抛出错误
- 能够省略包名,只用类名。beetl将搜索包路径找到合适的类(须要设置配置“IMPORT_PACKAGE=包名.;包名.”,包名后须要跟一个“.”, 或者调用Configuration.addPkg)方法具体请参考附件配置文件说明
- 内部类(包括枚举)访问同java同样,如User类有个内部枚举类Gender,访问是User$Gender
- 表达式是java风格,但参数仍然是beetl表达式,好比 @user.sayHello(user.name).这里user.sayHello是java调用,user.name 仍然是beetl表达式
若是在配置文件中设置了严格MVC,则如下语法将不在模板文件里容许,不然将报出STRICK_MVC 错误
经过重载AntlrProgramBuilder,能够按照本身的方法控制到底哪些语法是不容许在模板引擎中出现的,但这已经超出了Beetl模板的基础使用
指令格式为: DIRECTIVE 指令名 指令参数(可选) Beetl目前支持安全输出指令,分别是
<% DIRECTIVE DYNAMIC idList;
for(value in idList) .....
DYNAMIC 一般用在组件模板里,由于组件模板能够接收任何类型的对象。如列表控件,能够接收任何含有id和 value属性的对象。
- 注意 DYNAMIC 后的变量名也容许用引号,这主要是兼容Beetl1.x版本
- Beetl1.x 指令都是大写,当前版本也容许小写,如 directive dynamic idList
Beetl 本质上仍是强类型的模板引擎,即模板每一个变量类型是特定的,在模板运行过程当中,beetl 会根据全局变量自动推测出模板中各类变量和表达式类型。 也能够经过类型申明来讲明beetl全局变量的类型,以下格式
<%
/** *@type (List<User> idList,User user) */ for(value in idList) .....
类型申明必须放到多行注释里,格式是@type( … ),里面的申明相似java方法的参数申明。正如你看到的类型申明是在注释里,也就代表了这在Beetl模板引擎中不是必须的,或者你只须要申明一部分便可,之因此提供可选的类型说明,是由于
须要注意的是,若是在类型声明里提供的是类名,而不是类全路径,这样必须在配置文件里申明类的搜索路径((须要设置配置IMPORT_PACKAGE=包名.;包名.,或者调用Configuration.addPkg)),默认的搜索路径有java.util. 和 java.lang.
类型声明本用于eclipse插件用来提示,但eclipse插件暂时没有时间去作,因此类型申明如今不推荐使用
Beetl能较为详细的显示错误缘由,包括错误行数,错误符号,错误内容附近的模板内容,以及错误缘由,若是有异常,还包括异常和异常信息。 默认状况下,仅仅在控制台显示,以下代码:
<% var a = 1; var b = a/0; %>
运行此模板后,错误提示以下
>>DIV_ZERO_ERROR:0 位于3行 资源:/org/beetl/sample/s0125/error1.txt 1|<% 2|var a = 1; 3|var b = a/0; 4| %>
<% var a = 1; var b = a var c = a+2; %>
运行此模板后
>>缺乏符号(PARSER_MISS_ERROR):缺乏输入 ';' 在 'var' 位于4行 资源:/org/beetl/sample/s0125/error2.txt 1|<% 2|var a = 1; 3|var b = a 4|var c = a+2; 5| %>
- 默认的错误处理器仅仅像后台打印错误,并无抛出异常,若是须要在render错误时候抛出异常到控制层,则可使用org.beetl.core.ReThrowConsoleErrorHandler。不只打印异常,还抛出BeetlException
- 能够自定义异常处理器,好比把错误输出到 做为渲染结果一部分输出,或者输出更美观的html内容等,具体参考高级用法
- 能够在配置文件不设置异常,这样Beetl引擎将不处理异常,用户能够在外部来处理(能够在外部调用ErrorHandler子类来显示异常)
BeetlKit 提供了一些便利的方法让你马上能使用Beetl模板引擎。提供了以下方法
public static String render(String template, Map<String, Object> paras)
渲染模板,使用paras参数,渲染结果做为字符串返回public static void renderTo(String template, Writer writer, Map<String, Object> paras)
渲染模板,使用paras参数public static void execute(String script, Map<String, Object> paras)
执行某个脚本public static Map execute(String script, Map<String, Object> paras, String[] locals)
执行某个脚本,将locals指定的变量名和模板执行后相应值放入到返回的Map里public static Map executeAndReturnRootScopeVars(String script)
执行某个脚本,返回全部顶级scope的全部变量和值public static String testTemplate(String template, String initValue)
渲染模板template,其变量来源于intValue脚本运行的结果,其全部顶级Scope的变量都将做为template的变量String template = "var a=1,c=2+1;"; Map result = executeAndReturnRootScopeVars(template); System.out.println(result); //输出结果是{c=3, a=1}
BeetlKit 不要用于线上系统。仅仅做为体验Beetl功能而提供的,若是须要在线上使用这些功能,请参考该类源码自行扩展
Beetl建议经过配置文件配置GroupTemplate,主要考虑到IDE插件将来可能会支持Beetl模板,模板的属性,和函数等若是能经过配置文件获取,将有助于IDE插件识别。 配置GroupTemplate有俩种方法
配置文件分为三部分,第一部分是基本配置,在第一节讲到过。第二部分是资源类配置,能够在指定资源加载类,以及资源加载器的属性(这个配置在spring框架里,经过spring或者springboot的配置机制实现覆盖,并未起做用),以下
RESOURCE_LOADER=org.beetl.core.resource.ClasspathResourceLoader
#资源配置,resource后的属性只限于特定ResourceLoader #classpath 根路径 RESOURCE.root= / #是否检测文件变化 RESOURCE.autouCheck= true
第1行指定了模板加载器类,在beetl与其余框架集成的时候,模板加载器不必定根据这个配置,好比spring,它的RESOURCE_LOADER以spring的配置为准
第4行指定了模板根目录的路径,此处/ 表示位于classpath 根路径下,同loader同样,依赖使用的框架
第6行是否自动检测模板变化,默认为true,开发环境下自动检测模板是否更改。关于如何自定义ResouceLoader,请参考下一章
配置文件第三部分是扩展部分,如方法,格式化函数等
##### 扩展 ############## ## 内置的方法 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插件能识别这些注册函数
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,包含了模板的上下文,主要提供了以下属性
- call方法要求返回一个Object,若是无返回,返回null便可
- 为了便于类型判断,call方法最好返回一个具体的类,如date函数返回的就是java.util.Date
- call方法里的任何异常应该抛出成Runtime异常
尽管实现Function对于模板引擎来讲,是效率最高的方式,但考虑到不少系统只有util类,这些类里的方法仍然能够注册为模板函数。其规则很简单,就是该类的全部public方法。若是还须要Context 变量,则须要在方法最后一个参数加上Context便可,如
public class util{ public String print(Object a, Context ctx){ //balabala... } }
注意
- 从beetl效率角度来说,采用普通类效率不如实现Function调用
- 采用的普通java类尽可能少同名方法。这样效率更低。beetl调用到第一个适合的同名方法。而不像java那样找到最匹配的
- 方法名支持可变数组做为参数
- 方法名最后一个参数若是是Context,则beetl会传入这个参数。
能够不用写java代码,模板文件也能做为一个方法。默认状况下,须要将模板文件放到Root的functions目录下,且扩展名为.html(能够配置文件属性来修改这俩个默认值) 方法参数分别是para0,para1…..
以下root/functions/page.fn
<% //para0,para1 由函数调用传入 var current = para0,total = para1,style=para2!'simple' %> 当前页面 ${current},总共${total}
则在模板中
<%
page(current,total);
%>
容许使用return 表达式返回一个变量给调用者,如模板文件functions\now.html
<%
return date();
%>
在任何模板里均可以调用:
hello time is ${now(),'yyyy-MM-dd'}
也能够在functions创建子目录,这样function则具备namespace,其值就是文件夹名
须要实现Format接口
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,获取外的格式化信息。
public abstract Object format(Object data,String pattern,Context ctx);
标签形式有俩种,一种是标签函数,第二种是html tag。第二种实际上在语法解析的时候会转化成第一种,其实现是HTMLTagSupportWrapper,此类将会寻找root/htmltag目录下同名的标签文件做为模板来执行。相似普通模板同样,在此就不详细说了
标签函数相似jsp2.0的实现方式,须要实现Tag类的render方法便可
public class DeleteTag extends Tag{ @Override public void render(){ // do nothing,just ignore body ctx.byteWriter.write("被删除了,付费能够看") } }
如上一个最简单的Tag,将忽略tag体,并输出内容
public class XianDeDantengTag extends Tag{ @Override public void render(){ doBodyRender(); } }
此类将调用父类方法doBodyRender,渲染tag body体
public class CompressTag extends Tag{ @Override public void render(){ BodyContent content = getBodyContent(); String content = content.getBody(); String zip = compress(cotnent); ctx.byteWriter.write(zip); } }
此类将调用父类方法getBodyContent ,得到tag body后压缩输出
tag类提供了以下属性和方法供使用
能够为特定类注册一个虚拟属性,也能够为一些类注册虚拟属性
public void registerVirtualAttributeClass(Class cls, VirtualClassAttribute virtual) 实现VirtualClassAttribute方法能够为特定类注册一个须要属性,以下代码:
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方法将判断是否应用虚拟属性到此类
以下是虚拟属性类的定义
public interface VirtualClassAttribute{ public Object eval(Object o, String attributeName, Context ctx); } public interface VirtualAttributeEval extends VirtualClassAttribute{ public boolean isSupport(Class c, String attributeName); }
某些状况下,模板来源不止一处,GroupTemplate配置了一个默认的资源加载器,若是经过gt.getTemplate(key),将调用默认的ResourceLoader,获取模板内容,而后转化为beetl脚本放入到缓存里。你也能够传入额外的资源管理器加载模板,经过调用gt.getTemplate(key,otherLoader)来完成;
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; }
对于更复杂的模板资源来源,也能够自定义一个资源加载来完成,参考下一节
若是模板资源来自其余地方,如数据库,或者混合了数据库和物理文件,或者模板是加密的,则须要自定义一个资源加载器。资源加载器须要实现ResourceLoader类。以下:
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
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方法
@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下的全部模板,并注册为方法
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(一般数据库应该这么设计)来判断模板是否更改
public abstract class Resource{ /** * 打开一个新的Reader * * @return */ public abstract Reader openReader(); /** * 检测资源是否改变 * * @return */ public abstract boolean isModified();
参考例子能够参考beetl自带的ResourceLoader
组合加载器,能够包含多个已有的ResourceLoader,以下代码将建立一个包含俩个文件和内存的ResourceLoader
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文件内容
<%
include("/xxx2.html"){} include("http:/xxx.html"){} %>
第2行仍然是由fileLoader1加载,但第3行以http:前缀开头,所以将fileLoader2加载path2+/xxx.html.xxx.html内容以下
<%
include("db:1"){} %>
由于以db:开头,所以会采用MapResourceLoader加载,内容是key为db:1对模板
错误处理器须要实现ErrorHandler接口的processExcption(BeetlException beeExceptionos, Writer writer);
自定义错误处理多是有多个缘由,好比
想将错误输出到页面而不是控制台
错误输出美化一下,而不是自带的格式
错误输出的内容作调整,如不输出错误行的模板内容,而仅仅是错误提示
错误输出到日志系统里
不只仅输出日志,还抛出异常。默认自带的不会抛出异常,ReThrowConsoleErrorHandler 继承了ConsoleErrorHandler方法,打印异常后抛出
public class ReThrowConsoleErrorHandler extends ConsoleErrorHandler{ @Override public void processExcption(BeetlException ex, Writer writer){ super.processExcption(ex, writer); throw ex; } }
beetl 提供 ErrorInfo类来wrap BeetlException,转化为较为详细的提示信息,他具备以下信息
BeetlException 也包含了一个关键信息就是 resourceId,即出错所在的模板文件
全部模板的本地调用都须要经过安全管理器校验,默认须要实现NativeSecurityManager 的public boolean permit(String resourceId, Class c, Object target, String method) 方法
以下是默认管理器的实现方法
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; } }
groupTemplate.setSharedVars(Map<String, Object> sharedVars)
布局能够经过Beetl提供的include,layout 以及模板变量来完成。模板变量能完成复杂的布局
采用layout include
<%
//content.html内容以下: layout("/inc/layout.html"){ %> this is 正文 .......... <% } %>
如上一个子页面将使用layout布局页面,layout 页面内容以下
<% include("/inc/header.html"){} %> this is content:${layoutContent} this is footer:
layoutContent 是默认变量,也能够改为其余名字,具体请参考layout标签函数
全局变量老是能被布局用的页面所使用,若是布局页面须要临时变量,则须要显示的传入,如:
<%
var user= model.user; include("/inc/header.html",{title:'这是一个测试页面',user:user}){} %>
这样,title和user成为全局变量,能被header.html 及其子页面引用到
继承布局:采用模板变量和include
<% var jsPart = { %> web页面js部分 <% }; %> <% var htmlPart = { %> web页面html部分 <% }; include("/inc/layout.html",{jsSection:jsPart,htmlSection:htmlPart}){} %>
layout.html页面以下:
<body> <head> ${jsSection} </head> <body> ....... ${htmlSection} </body>
Beetl性能已经很快了,有些策略能更好提升性能
为何Beetl性能这么好…………(待续)
Beetl模板引擎模板在同一个虚拟机里缓存Beetl 脚本。也能够将缓存脚本到其余地方,只要实现Cache接口,并设置ProgramCacheFactory.cache便可,这样GroupTemplate将从你提供的Cache中存取Beetl脚本
此功能未被很好测试
占位符输出容许定制。如全部日期类型都按照某个格式化输出,而不需显式的使用格式化输出,或者为了防止跨脚本站点攻击,须要对类型为String的值作检查等,没必要使用格式化函数,能够直接对占位符输出进行定制,代码以下
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 来输出
Beetl在线体验(http://ibeetl.com/beetlonline/)面临一个挑战,容许用户输入任何脚本作练习或者分享代码。但又须要防止用户输入恶意的代码,如
<%
for(var i=0;i<10000000;i++){ //其余代码 } %>
此时,须要定制模板引擎,遇到for循环的时候,应该限制循环次数,譬如,在线体验限制最多循环5次,这是经过定义替换GeneralForStatement类来完成的,这个类对应了for(exp;exp;exp) ,咱们须要改为以下样子:
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,
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出一个脚本作分析优化,所以,俩个脚本都须要作修改
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; } }
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,则不需替换。这一般发生在你仅仅经过修改该类的某些属性就能够的场景
完成这些代码后,在配置文件中申明使用新的引擎
ENGINE=org.bee.tl.online.VarRefTemplateEngine
这样就完成了模板引擎定制。
另一种定制模板引擎方法(2.7.22)
在2.7.21 版本后,提供了另一种定制模板引擎的方法,能够在Beetl语法树生成的时候提供定制(上面那种是在生成后),这种方法更灵活。但须要对语法树有所了解。
首先须要建立一个引擎
ENGINE=org.bee.tl.online.VarRefTemplateEngine
OnlineTemplateEngine 代码以下,
public class VarRefTemplateEngine extends DefaultTemplateEngine { protected AntlrProgramBuilder getAntlrBuilder(GroupTemplate gt){ AntlrProgramBuilder pb = new AntlrProgramBuilder(gt); return pb; } class VarRefAntlrProgramBuilder extends AntlrProgramBuilder{ public VarRefAntlrProgramBuilder(GroupTemplate gt) { super(gt); } } }
AntlrProgramBuilder 方法用于构造语法树,有多个Protected方法能够重载,以实现新的实现。
Beetl模板本质上会转化为Beetl脚原本执行,这点跟jsp转为servlet来执行相似。GroupTemplate提供方法能够直接执行Beetl脚本
key为资源名,paras为脚本的全局变量,w可选参数,若是执行脚本有输出,则输出到w里,loader参数可选,若是指定,则使用此laoder加载脚本
执行脚本完毕后,返回到Map里的值可能包含以下:
以下脚本(此时就不须要脚本定界符了)
var a = 1; var b = date(); var c = '2'; return a+1;
调用runScript后,map里将返回key分别为a,b,c,return。 值分别为1,当前日期,字符串'2,以及3。
GroupTemplate 提供了validateTemplate和 validateScript方法用来校验模板,若是模板或者脚本有语法错误,则返回BeetlException,BeetlException包含了错误的具体信息,能够参考ConsoleErrorHandler来了解如何处理异常,以下是一个简单的处理片断
BeetlException ex = groupTemplate.validateTemplate("/index.html"); if(ex==null){ return } ErrorInfo error = new ErrorInfo(ex); int line = error.getErrorTokenLine(); String errorToken = error.getErrorTokenText(); String type = error.getType();
Web集成模块向模板提供web标准的变量,作以下说明
你能够在模板任何地方访问这些变量
若是你须要扩展更多属性,你也能够配置beetl.properties配置文件的WEBAPP_EXT属性,实现WebRenderExt接口,在渲染模板以前增长本身的扩展,如:
RESOURCE.root=/WEB-INF/views WEBAPP_EXT = com.park.oss.util.GlobalExt
public class GlobalExt implements WebRenderExt{ static long version = System.currentTimeMillis(); @Override public void modify(Template template, GroupTemplate arg1, HttpServletRequest arg2, HttpServletResponse arg3) { //js,css 的版本编号 template.binding("sysVersion",version); } }
这样,每次在模板里均可以访问变量sysVersion了,不须要在controller里设置,或者经过servlet filter来设置
Beetl默认提供了WebRender用于帮助web集成开发,全部内置的集成均基于此方法。若是你认为Beetl内置的各个web框架集成功能不够,你能够继承此类,或者参考此类源码从新写,其代码以下
package org.beetl.ext.web; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; import java.util.Enumeration; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.beetl.core.GroupTemplate; import org.beetl.core.Template; import org.beetl.core.exception.BeetlException; /** * 一般web渲染的类,将request变量赋值给模板,同时赋值的还有session,request,ctxPath * 其余框架能够继承此类作更多的定制 * @author joelli * */ public class WebRender{ GroupTemplate gt = null; public WebRender(GroupTemplate gt){ this.gt = gt; } /** * @param key 模板资源id * @param request * @param response * @param args 其余参数,将会传给modifyTemplate方法 */ public void render(String key, HttpServletRequest request, HttpServletResponse response, Object... args){ Writer writer = null; OutputStream os = null; try{ //response.setContentType(contentType); Template template = gt.getTemplate(key); Enumeration<String> attrs = request.getAttributeNames(); while (attrs.hasMoreElements()){ String attrName = attrs.nextElement(); template.binding(attrName, request.getAttribute(attrName)); } WebVariable webVariable = new WebVariable(); webVariable.setRequest(request); webVariable.setResponse(response); webVariable.setSession(request.getSession()); template.binding("session", new SessionWrapper(webVariable.getSession())); template.binding("servlet", webVariable); template.binding("request", request); template.binding("ctxPath", request.getContextPath()); modifyTemplate(template, key, request, response, args); String strWebAppExt = gt.getConf().getWebAppExt(); if(strWebAppExt!=null){ WebRenderExt renderExt = this.getWebRenderExt(strWebAppExt); renderExt.modify(template, gt, request, response); } if (gt.getConf().isDirectByteOutput()){ os = response.getOutputStream(); template.renderTo(os); }else{ writer = response.getWriter(); template.renderTo(writer); } } catch (IOException e){ handleClientError(e); } catch (BeetlException e){ handleBeetlException(e); } finally{ try{ if (writer != null) writer.flush(); if (os != null) os.flush(); } catch (IOException e){ handleClientError(e); } } } /** * 能够添加更多的绑定 * @param template 模板 * @param key 模板的资源id * @param request * @param response * @param args 调用render的时候传的参数 */ protected void modifyTemplate(Template template, String key, HttpServletRequest request, HttpServletResponse response, Object... args){ } /**处理客户端抛出的IO异常 * @param ex */ protected void handleClientError(IOException ex){ //do nothing } /**处理客户端抛出的IO异常 * @param ex */ protected void handleBeetlException(BeetlException ex){ throw ex; } }
只须要在Servlet代码里引用ServletGroupTemplate就能集成Beetl,他提供了一个render(String child, HttpServletRequest request, HttpServletResponse response)方法。例子以下:
protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); //模板直接访问users request.setAttribute("users",service.getUsers()); ServletGroupTemplate.instance().render("/index.html", request, response); }
ServletGroupTemplate同其余web集成同样,将读取配置文件来配置,若是须要经过代码配置,能够在Servlet listener里 ServletGroupTemplate.instance().getGroupTemplate()方法获取GroupTemplate
须要作以下配置便可
<bean id="beetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"/> <bean id="viewResolver" class="org.beetl.ext.spring.BeetlSpringViewResolver"> <property name="contentType" value="text/html;charset=UTF-8"/> </bean>
同其余集成方式同样,模板的配置将放在beetl.properties中。
若是想获取GroupTemplate,能够调用以下代码
BeetlGroupUtilConfiguration config = (BeetlGroupUtilConfiguration) this.getApplicationContext().getBean("beetlConfig"); GroupTemplate group = config.getGroupTemplate();
Controller代码以下:
@RequestMapping(value = "/", method = RequestMethod.GET) public ModelAndView index(HttpServletRequest req) { ModelAndView view = new ModelAndView("/index"); //total 是模板的全局变量,能够直接访问 view.addObject("total",service.getCount()); return view; }
http://git.oschina.net/xiandafu/springbeetlsql 有完整例子
一般能够把模板放到WEB-INF目录下,除了能够配置beetl.propertis 外,还可使用Spring配置
<bean id="beetlConfig" class="org.beetl.ext.spring." init-method="init"> <property name="root" value="/WEB-INF/templates"/> </bean>
spring集成还容许注册被spring容器管理的Function,Tag等,也容许配置多个视图解析器等功能
<bean name="beetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"> <property name="configFileResource" value="/WEB-INF/beetl.properties"/> <property name="functions"> <map> <entry key="testFunction" value-ref="testFunction"/> </map> </property> <property name="functionPackages"> <map> <entry key="fp" value-ref="testFunctionPackage"/> </map> </property> <property name="tagFactorys"> <map> <entry key="html.output" value-ref="testTagFactory"/> <entry key="html.output2" value-ref="testTagFactory2"/> </map> </property> </bean> <bean name="testTagFactory" class="org.beetl.ext.spring.SpringBeanTagFactory"> <property name="name" value="testTag"/> </bean> <bean name="testTagFactory2" class="org.beetl.ext.spring.SpringBeanTagFactory"> <property name="name" value="testTag2"/> </bean> <bean name="beetlViewResolver" class="org.beetl.ext.spring.BeetlSpringViewResolver"> <property name="config" ref="beetlConfig"/> <property name="contentType" value="text/html;charset=UTF-8"/> </bean>
如上图所示,BeetlGroupUtilConfiguration有不少属性,列举以下
@Service @Scope("prototype") public class TestTag extends Tag { }
以下配置,指定了三个视图解析器,一个用于beetl页面渲染,一个用于cms,采用了beetl技术,另一个是一些遗留的页面采用jsp
<bean name="beetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"> <property name="configFileResource" value="/WEB-INF/beetl.properties"/> </bean> <bean name="cmsbeetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"> <property name="configFileResource" value="/WEB-INF/cms-beetl.properties"/> </bean> <!-- Beetl视图解析器1 --> <bean name="beetlViewResolver" class="org.beetl.ext.spring.BeetlSpringViewResolver"> <!-- 多视图解析器,须要设置viewNames和order --> <property name="viewNames"> <list> <value>/template/**</value> </list> </property> <property name="suffix" value=".btl"/> <property name="contentType" value="text/html;charset=UTF-8"/> <property name="order" value="0"/> <!-- 多GroupTemplate,须要指定使用的bean --> <property name="config" ref="beetlConfig"/> </bean> <!-- Beetl视图解析器2 --> <bean name="cmsBeetlViewResolver" class="org.beetl.ext.spring.BeetlSpringViewResolver"> <!-- 多视图解析器,须要设置viewNames和order --> <property name="viewNames"> <list> <value>/cmstemplate/**</value> </list> </property> <property name="contentType" value="text/html;charset=UTF-8"/> <property name="order" value="1"/> <!-- 多GroupTemplate,须要指定使用的bean --> <property name="config" ref="cmsbeetlConfig"/> </bean> <!-- JSP视图解析器 --> <bean name="JSPViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 注意JSP的这个视图解析器order必须在最后 --> <property name="order" value="256"/> <!-- beetl配置不支持前缀,这不一样于jsp 和 freemaker --> <property name="prefix" value="/WEB-INF/"/> <property name="suffix" value=".jsp"/> <property name="contentType" value="text/html;charset=UTF-8"/> </bean>
Beetl视图解析器属性同spring自带的视图解析器同样,支持contentType,order,prefix,suffix等属性。
注意视图解析器里的属性viewNames,这个用于判断controller返回的path到底应该交给哪一个视图解析器来作。
你也能够经过扩展名来帮助Spring决定采用哪一种视图解析器,好比
<property name="viewNames"> <list> <value>/**/*.btl</value> </list> </property>
若是你想更改此规则,你只能增长canHandle方法指定你的逻辑了。详情参考org.springframework.web.servlet.view.UrlBasedViewResolver.canHandle
对于仅仅须要redirect和forward的那些请求,须要加上相应的前缀
其余集成须要注意的事项:
<bean name="cmsbeetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"> <property name="root" value="/WEB-INF/views"/> </bean>
<dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl-framework-starter</artifactId> <version>1.1.55.RELEASE</version> </dependency>
starter 自动处理以btl结尾的视图,模板根目录是Spring Boot默认的templates目录。以下配置能够修改beetl部分属性
Starter能够实现BeetlTemplateCustomize来定制Beetl
@Configuration public MyConfig{ @Bean public BeetlTemplateCustomize beetlTemplateCustomize(){ return new BeetlTemplateCustomize(){ public void customize(GroupTemplate groupTemplate){ } }; } }
使用Starter来配置已经够用,若是你想本身配置模板引擎, 经过java config来配置 beetl须要的BeetlGroupUtilConfiguration,和 BeetlSpringViewResolver,参考代码以下
@Configuration public class BeetlConf { @Value("${beetl.templatesPath}") String templatesPath;//模板根目录 ,好比 "templates" @Bean(name = "beetlConfig") public BeetlGroupUtilConfiguration getBeetlGroupUtilConfiguration() { BeetlGroupUtilConfiguration beetlGroupUtilConfiguration = new BeetlGroupUtilConfiguration(); //获取Spring Boot 的ClassLoader ClassLoader loader = Thread.currentThread().getContextClassLoader(); if(loader==null){ loader = BeetlConf.class.getClassLoader(); } beetlGroupUtilConfiguration.setConfigProperties(extProperties);//额外的配置,能够覆盖默认配置,通常不须要 ClasspathResourceLoader cploder = new ClasspathResourceLoader(loader, templatesPath); beetlGroupUtilConfiguration.setResourceLoader(cploder); beetlGroupUtilConfiguration.init(); //若是使用了优化编译器,涉及到字节码操做,须要添加ClassLoader beetlGroupUtilConfiguration.getGroupTemplate().setClassLoader(loader); return beetlGroupUtilConfiguration; } @Bean(name = "beetlViewResolver") public BeetlSpringViewResolver getBeetlSpringViewResolver(@Qualifier("beetlConfig") BeetlGroupUtilConfiguration beetlGroupUtilConfiguration) { BeetlSpringViewResolver beetlSpringViewResolver = new BeetlSpringViewResolver(); beetlSpringViewResolver.setContentType("text/html;charset=UTF-8"); beetlSpringViewResolver.setOrder(0); beetlSpringViewResolver.setConfig(beetlGroupUtilConfiguration); return beetlSpringViewResolver; } }
注意:这里并无配置后缀,所以controller代码里必须显式的加上后缀
//return "/hello" 错误用法 return "hello.html"
注意,能够经过Application.properties 配置以下属性禁用BeetlSQL或者禁用Beetl
beetlsql.enabled=false beetl.enabled=false
须要配置web.xml,将全部请求交给jodd处理,参考:http://jodd.org/doc/madvoc/setup.html
<filter> <filter-name>madvoc</filter-name> <filter-class>jodd.madvoc.MadvocServletFilter</filter-class> <init-param> <param-name>madvoc.webapp</param-name> <param-value>test.MyWebApplication</param-value> </init-param> <init-param> <param-name>madvoc.configurator</param-name> <param-value>test.MyAutomagicMadvocConfigurator</param-value> </init-param> </filter> <filter-mapping> <filter-name>madvoc</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
MyWebApplication 和 MyAutomagicMadvocConfigurator 须要本身参照以下例子写一个,前者用来设置beetl做为视图渲染,后者配置Jodd不要扫描beetl struts集成里引用的struts类
public class MyAutomagicMadvocConfigurator extends AutomagicMadvocConfigurator { public MyAutomagicMadvocConfigurator(){ super(); //不扫描beetl 里jar文件里的action和result,不然,会扫描StrutsResultSupport不相干的class this.rulesJars.exclude("**/*beetl*.jar"); } }
public class MyWebApplication extends WebApplication{ @Override protected void init(MadvocConfig madvocConfig, ServletContext servletContext) { //设置默认 madvocConfig.setDefaultActionResult(BeetlActionResult.class); } }
最后,能够写Action了,浏览器输入/index.html,jodd将执行world方法,并渲染ok.html模板。若是你想配置GroupTemplate,正如其余集成框架同样,只须要写一个beetl.properties 便可。
@MadvocAction public class IndexAction { @Out String value; @Action("/index.html") public String world() { value = "Hello World!"; return "/ok.html"; } }
https://git.oschina.net/xiandafu/beetl-jodd-sample 有完整例子
Beetl提供 JFinal3.0 集成,使用JFinal3BeetlRenderFactory ,经过以下注册便可使用beetl模板引擎
public class DemoConfig extends JFinalConfig { public void configConstant(Constants me) { PropKit.use("a_little_config.txt"); // 加载少许必要配置,随后可用PropKit.get(...)获取值 me.setDevMode(PropKit.getBoolean("devMode", false)); JFinal3BeetlRenderFactory rf = new JFinal3BeetlRenderFactory(); rf.config(); me.setRenderFactory(rf); GroupTemplate gt = rf.groupTemplate; //根据gt能够添加扩展函数,格式化函数,共享变量等, }
业务逻辑代码:
public void modify(){ int artId = getParaToInt(0, -1); setAttr("title", "修改文章"); List<Cate> cateLists = Cate.getAllCate(); //模板里访问cateLists,atr, setAttr("cateLists", cateLists); setAttr("art", Article.dao.findById(artId)); render("/modify.html"); }
BeetlRenderFactory 默认使用FileResourceLoader ,其根目录位于WebRoot目录下,若是你须要修改到别的目录,能够设置配置文件,如
RESOURCE.root= /WEB-INF/template/
https://git.oschina.net/xiandafu/beetl-jfinal-sample 有完整例子,采用jfinal+beetl写的一个博客系统
https://git.oschina.net/xiandafu/jfinal_beet_beetsql_btjson 同上,但DAO部分采用了BeetlSql
JFinal3 与 Jfinal2.0不兼容,且不像Appache Common Lang那样,不兼容状况下采用不一样的包名,致使了Beetl不能同时兼容:若是想在beetl(2.7.10)之后版本中仍然使用Jfinal2,须要本身写集成代码,本身写一个IMainRenderFactory的实现类
//Jfinal2 集成 import java.io.IOException; import org.beetl.core.Configuration; import org.beetl.core.GroupTemplate; import org.beetl.core.ResourceLoader; import org.beetl.core.resource.WebAppResourceLoader; import com.jfinal.kit.PathKit; import com.jfinal.render.IMainRenderFactory; import com.jfinal.render.Render; public class Jfinal2BeetlRenderFactory implements IMainRenderFactory { public static String viewExtension = ".html"; public static GroupTemplate groupTemplate = null; public Jfinal2BeetlRenderFactory() { init(PathKit.getWebRootPath()); // init(null); use jfinalkit instead } public Jfinal2BeetlRenderFactory(ResourceLoader resourceLoader) { if (groupTemplate != null) { groupTemplate.close(); } try { Configuration cfg = Configuration.defaultConfiguration(); groupTemplate = new GroupTemplate(resourceLoader, cfg); } catch (IOException e) { throw new RuntimeException("加载GroupTemplate失败", e); } } public Jfinal2BeetlRenderFactory(String templateRoot) { init(templateRoot); } private void init(String root) { if (groupTemplate != null) { groupTemplate.close(); } try { Configuration cfg = Configuration.defaultConfiguration(); WebAppResourceLoader resourceLoader = new WebAppResourceLoader(root); groupTemplate = new GroupTemplate(resourceLoader, cfg); } catch (IOException e) { throw new RuntimeException("加载GroupTemplate失败", e); } } public Render getRender(String view) { return new BeetlRender(groupTemplate, view); } public String getViewExtension() { return viewExtension; } }
业务逻辑代码:
import org.beetl.core.GroupTemplate; import org.beetl.core.exception.BeetlException; import org.beetl.ext.web.WebRender; import com.jfinal.render.Render; import com.jfinal.render.RenderException; //Jfinal2 集成 public class BeetlRender extends Render { GroupTemplate gt = null; private transient static final String encoding = getEncoding(); private transient static final String contentType = "text/html; charset=" + encoding; public BeetlRender(GroupTemplate gt, String view) { this.gt = gt; this.view = view; } @Override public void render() { try { response.setContentType(contentType); WebRender webRender = new WebRender(gt); webRender.render(view, request, response); } catch (BeetlException e) { throw new RenderException(e); } } }
而后在Jfinal2里配置完成
import org.beetl.ext.jfinal.BeetlRenderFactory public class DemoConfig extends JFinalConfig{ public void configConstant(Constants me){ me.setMainRenderFactory(new Jfinal2BeetlRenderFactory()); // 获取GroupTemplate ,能够设置共享变量等操做 GroupTemplate groupTemplate = Jfinal2BeetlRenderFactory.groupTemplate ; } }
Nutz集成提供了 BeetlViewMaker ,实现了 ViewMaker方法,以下代码
@At("/ctx") @Ok("beetl:ctx.btl") public Context withContext() { Context ctx = Lang.context(); Pager pager = dao.createPager(1, 20); pager.setRecordCount(dao.count(UserProfile.class)); List<UserProfile> list = dao.query(UserProfile.class, null, pager); ctx.set("pager", pager); ctx.set("list", list); return ctx; }
<html> <head> <title>Beetl&Nutz</title> </head> <body> <p>总共 ${list.~size}<p/> <% for(user in list){ %> <p>hello,${user.nickname};<p/> <% } %> <p>当前页${pager.pageNumber},总共${pager.pageCount}页<p/> </body> </html>
须要注意的是,若是使用了nutz的obj(http://www.nutzam.com/core/mvc/view.html),则须要在模板顶部申明obj是动态对象,如
<%
directive dynamic obj
%> ${obj.user.title} ${obj.user.name}
或者使用beetl的默认引擎,采起以下配置
ENGINE=org.beetl.core.engine.DefaultTemplateEngine
须要在struts2配置文件里添加result-types作以下配置
<package name="default" namespace="/" extends="struts-default"> <!-- .... --> <result-types> <result-type name="beetl" class="org.beetl.ext.struts2.Struts2BeetlActionResult" default="true" > <param name="contentType">text/html; charset=UTF-8</param> </result-type> </result-types> <action name="HelloWorld" class="com.beetl.struts.HelloWorld"> <result>/hello.html</result> </action> <action name="Ajax" class="com.beetl.struts.AjaxHtml"> <result>/table.html#table</result> </action> <!-- .... --> </package>
该类会根据struts配置文件获取模板,如上例的hello.html,并将formbean的属性,以及request属性做为全局变量传递给模板
https://git.oschina.net/xiandafu/beetl-struts2-sample 有完整例子
Struts2.5 自己作了包名调整,所以自从Beetl2.8.0之后,只支持Struts2.5.x以上版本,这个版本安全漏洞少.... :)
对于web应用来讲,必须经过controller才能渲染模板,beetl也能够写完模板后,在未完成controller状况下,直接渲染模板 此方法既能够做为一般的全栈式开发人员使用,也能够用于前端人员单独开发模板用。 Beetl使用WebSimulate来模拟模板渲染或者REST请求返回json数据,WebSimulate 会取出请求路径,而后执行values目录下同一个请求路径的脚本,脚本的顶级变量都将做为全局变量,并渲染请求路径同名的的模板文件。 好比请求路径是http://127.0.0.1:8080/user/userlist.html, 则WebSimulate会执行/values/user/userlist.html.var 脚本,获取到全部顶级变量,并渲染/user/userlist.html 页面 若是脚本定义了名为json的变量,则WebSimulate 返回的是json数据,不然,则是模板渲染 若是脚本里还定义了ajax变量,则认为是局部渲染,ajax变量由于字符串,就是表明ajaxId WebSimulate容许使用path变量,且在values目录下,用$$代替,好比对于REST请求 /user/1,若是在values目录下有/values/users/$$.var, 则能匹配上此模拟脚本 WebSimulate对应到脚本的时候,容许根据HTTP METHOD对应,好比一个REST的GET请求 /user/1,能够对应/values/user/$$.get.var 对应的关系,老是精确匹配优先,对于/user/1,优先精确匹配/user/1.var,其次是/user/$$.get.var, 最后才是/user/$$.var 则WebSimulate 在执行脚本的时候,老是先读取/values/common.var, 以得到须要的公共变量
安装WebSimulate较为简单,以springboot为例子
@Controller @RequestMapping("/simulate") public class SimulateController { @Autowired WebSimulate webSimulate; @RequestMapping("/**/*.html") public void simluateView(HttpServletRequest request,HttpServletResponse response){ webSimulate.execute(request, response); } @RequestMapping("/api/**") public void simluateJson(HttpServletRequest request,HttpServletResponse response){ webSimulate.execute(request, response); } }
如上,全部以/smulate 开头的请求,都会使用模拟数据来支持分离开发,其中simluateView来模拟视图渲染,simluateJson来模拟REST请求的数据
WebSimulate 初始化代码以下
@Bean public WebSimulate getWebSmulate(BeetlSpringViewResolver resolver){ WebSimulate webSimulate = new WebSimulate(resolver.getConfig().getGroupTemplate()){ public String getValuePath(HttpServletRequest request){ return this.removePreffix( request.getServletPath()); } protected String getRenderPath(HttpServletRequest request) { return this.removePreffix( request.getServletPath()); } private String removePreffix(String path){ return path.replaceFirst("/simulate", ""); } }; return webSimulate; }
WebSimulate 一般能够直接使用,但本例子中,为了彻底模拟,须要去掉/simulate",这样没必要要建立一个/values/simulate
如上配置完毕,若是普通模板请求
/simulate/user/userlist.html
将会执行/values/user/userlist.html.var 的脚本,好比,模拟users数据
var users = [{"name":"xiandafu"},{"name":"lucy"}];
若是一个REST请求
/simulate/api/user/1
能够建立以下文件/values/api/user/$$.get.var,内容直接返回一个json字符串
var json = "{'success':true}";
WebSimulate 构造的时候须要一个实现JsonUtil的类(Beetl并不自带json序列化工具),这样,对于要返回的json数据,能够没必要向上面的例子那样,返回json字符串,能够返回一个对象,如Map,而后交给jsonUtil来序列化返回客户端
脚本自己能够获取模拟请求的参数,如session,parameter等,从而灵活的模拟数据,具体请参考WebSimulate源码
直接访问模板前提是使用了伪模型,这与实际的项目采用的模型并不一致,所以当模板采用伪模型验证后,须要重启web应用,才能使用真正的模型去测试,不然,模板引擎会报错,这是由于beetl默认的FastRuntimeEngine会根据模型优化模板,对同一个模板不一样的模型会报错,除非采用DefaultTemplateEngine 或者页面申明类型变量是动态的。
愈来愈多web网站依赖于ajax,如table的翻页,流行方式是浏览器发出ajax请求,后台处理后返回一个json,浏览器端将json数据拆开,拼成一条一条的行数据,而后生成dom节点,追加到表格里。 做为另一种可选技术,beetl支持局部渲染技术,容许后台处理返回的是一个完成的html片断,这样,前端浏览器能够直接将这个html片断追加到表格里。在我作的性能测试里,俩种方式性能差异不大(http://bbs.ibeetl.com/ajax//)
好比模板index.html有不少动态内容,有动态生成的菜单,有右侧的top10,也有核心区域的表格,大概内容以下
<#menu/> <#top10> ....</#top10> <div id="table-container" > <% //ajax片断开始 #ajax userTable: { %> <table> <tr><td width=100>id</td><td width=100>姓名</td></tr> <% for(user in users){ %> <tr><td>${user.id}</td><td>${user.name}</td></tr> <% } %> </table> 当前页面<span id="current">${page!1}</span><span style="width:20px"></span> <a href="#"><span class="page">next</span></a> <a href="#" ><span class="page">pre</span></a> <% //ajax片断结尾 } %>
#ajax 用于告诉告诉模板引擎,此处是个局部渲染标记,标记为"userTable",对于正常渲染视图"index.html"页面,#ajax标记没什么用处,table仍能获得正常渲染。若是渲染的视图是index.html#userTable,则模板只会渲染#ajax标记得模板片断,其余部分将忽略。关于完整例子,能够参考https://git.oschina.net/xiandafu/beetlajax
后台代码以下:
render("/index.html#userTable");
只须要在模板路径后加上#就表示渲染的并不是是整个模板,而是模板的一部分,这一部分由#后面的标记来标示
ajax 片断渲染也支持默认状况下不渲染,仅仅作为一个片断使用,如一个页面有许多后台交互操做,并返回相应的html片断,能够将这些html片断也放到同一个模板里,使用ajax norender,表示渲染整个模板的时候默认并不须要渲染此ajax片断
<%
<html>
</html>
#ajax norender success: { %> <div id="success"> 操做成功 </div> <% } %> #ajax norender failure: { %> <div id="failure"> 操做失败 </div> <% } %>
这样,此页面默认状况下并无输出success,和 failure片断
注意,Ajax片断本质上是从模版的ajax标记处开始渲染,所以,ajax须要的变量在模版里也必须是全局变量,若是你只是个局部变量,beetl会报出找不到变量,即便你binding了这个变量,beetl也认为这个是局部变量,如
<% var tableData = paras.table; #ajax userTable: { for(user in tableData); %> <% //ajax片断结尾 } %>
变量tableData是从paras里获取的,是个临时变量,所以就算你在后台binding了一个tableData,beetl 也不能识别。在渲染ajax片断的时候会报变量tableData找不到。改正的办法只能是让tableData全局变量。
返回Json好仍是返回html片断好?这个难以定论.
- 从后台性能看,将模型序列化成json性能会比渲染模板性能更好,可是,json还须要前端从新解析生成最终html dom节点,这可能会延迟最终数据的现实效果。而返回的html片断就是已经生成好的dom
- 从网络传入来看,json无疑更好的,html片断会有额外的html标记,css属性,以及有可能的js调用。传入流量有可能增长50%到100%。可是,对于web应用类,这些额外数据,并不算多。
- 从开发效率来说,返回html片断的开发效率更高一些,由于渲染在后台操做,能够为所欲为的用模板语言来渲染,来取得后台数据,完成复杂渲染,而json就比较困难,能够说全部的json lib都没有完美的解决办法。
- 从用户体验上来说,Beetl 采用ajax标记,混合了传统的模板渲染和ajax加载。用户进入页面即能看到数据,而经典的ajax json方式还须要异步加载,显示延迟。另外若是页面同时有多个ajax加载,则会对服务器形成很大的压力。
- 关心服务器cpu消耗? 模板方式消耗更多的cpu,json方式则少点。可是俩者差距并不大。并且更多的web网站面临的状况是有富余的服务器CPU能力
- 关心客户端CPU消耗? 过多的js无疑是客户端运行慢的主要缘由。若是采用经典的json方式,返回的json数据必然还须要通过js的计算和渲染。会影响客户机器cpu。
符号#ajax 实际上用来标记一个模板渲染片断,它还有个别名的叫#fragment,二者是同样的,好比
<%
#fragment part2:{ println("part2"); } %>
2.2.3版本之后,新增长org.beetl.ext.web.WebErrorHandler,能够在web开发的时候在页面输出提示信息,在产品模式下在后台输出提示信息(经过配置属性ESOURCE.autoCheck= true来认为是开发模式),仅仅须要配置以下:
ERROR_HANDLER = org.beetl.ext.web.WebErrorHandler
strutil方法对参数均不作空指针检测,你可自定义方法来覆盖这些内置的方法
Spring函数并无内置,须要注册,以下
<bean name="beetlGroupUtilConfiguration" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"> <property name="functions"> <map> <!-- 定义SpEL方法 --> <entry key="spel"> <bean class="org.beetl.ext.spring.SpELFunction"/> </entry> </map> </property> <property name="functionPackages"> <map> <entry key="sputil"> <bean class="org.beetl.ext.spring.UtilsFunctionPackage"/> </entry> </map> </property> </bean>
spel(spelString, rootObject) SpEL方法传入一个Spring SpEL表达式以获取表达式结果,方法建议以函数的方式定义在BeetlGroupUtilConfiguration的functions中
spelString: SpEL表达式字符串,必传(不然返回null) rootObject: 做为spel的根对象(对应#root),能够是一个Map或Bean对象,默认取空Map。因为Beetl运行上下文没法直接获取模版局部变量的变量名,建议局部变量采用自定义Map的方式传入
列表筛选(以自定义Map为根对象传入局部变量)
<% var intArray = [12, 1, 2, 3]; %>
${spel('#root.intArray.?[#this>10]', {intArray: intArray})}
以Bean对象为根对象
<% var now = date(); %>
${spel('#root.year + 1900', now)}
直接new对象
${spel('(new java.util.Date()).year + 1900')}
直接引用Spring Bean
${spel('@testBean')}
默认变量
#root 表示SpEL的根对象, 由spel函数第二参数传入,默认是一个空map
#context 表示Beetl执行上下文
#global 表示Beetl的共享变量Map,因为Beetl上下文没法获取临时变量名,临时变量建议使用根对象的方式传入
#ctxPath 表示Servlet Context Path(由Beetl WebRender提供)
#servlet 能够从中获取到Servlet request,response,session原生实例(由Beetl WebRender提供)
#parameter 表示请求参数Map(由Beetl WebRender提供)
#request 表示请求对象(由Beetl WebRender提供)
#session 表示会话域属性Map(由Beetl WebRender提供)
sputil 提供了spring内置的一些功能,如
// 测试source中是否包含了candidates的某个成员(至关于交集非空) sputil.containsAny(Collection<?> source, Collection<?> candidates) // 返回在source集合总第一个也属于candidates集的元素 sputil.findFirstMatch(Collection<?> source, Collection<?> candidates) // 测试指定文本是否匹配指定的Ant表达式(\*表达式), 多个表达式只要一个匹配便可 sputil.antMatch(String input, String... patterns) // 返回指定路径表示的文件的扩展名(不带点.) sputil.fileExtension(String path) // 忽略大小写的endsWith sputil.endsWithIgnoreCase(String input, String suffix) // 忽略大小写的startsWith sputil.startsWithIgnoreCase(String input, String prefix) // 测试输入值是否为空白, null视为空白, 无视字符串中的空白字符 sputil.isBlank(String input) // 首字母大写转换 sputil.capitalize(String input) // 首字母小写转换 sputil.uncapitalize(String input) // 在集合或数组元素之间拼接指定分隔符返回字符串 // null表示空集, 其余类型表示单元素集合 sputil.join(Object collection, String delim) // 同上, 只是会在最后结果先后加上前缀和后缀 // 注意这个函数名叫作joinEx sputil.joinEx(Object collection, String delim, String prefix, String suffix) // 对文本进行html转义 sputil.html(String input) // 对文本进行javascript转义 sputil.javaScript(String input)
下列三个函数只需以函数的方式定义在BeetlGroupUtilConfiguration的functions中便可,与spel函数同样的,函数名声明在functions中,能够更改
urlIf(\<url>, \<method>) 对应类: org.beetl.ext.spring.AccessUrlIfFunction 参数: url: 字符串表示的测试URL Path,不须要指定Context Path,缺省会直接返回true method: 字符串表示的访问方式, 默认为GET, 建议全大写 返回值: 测试当前登陆用户是否能访问指定的URL Path, 返回true or false
示例:
urlIf('/system/admin_update.do', 'POST'))
若是当前环境不存在Spring Security安全上下文,将返回true 若是当前环境不存在用户认证凭证,做为匿名登陆进行测试
expIf(\<exp>) 对应类: org.beetl.ext.spring.AccessExpressionIfFunction 参数: exp: Spring Security安全表达式,缺省会直接返回true 返回值: 测试当前登陆用户是否知足指定的安全表达式,返回true or false 示例:
expIf('isAuthenticated()')
若是当前环境不存在Spring Security安全上下文,将返回true 若是当前环境不存在用户认证凭证,做为匿名登陆进行测试
注意: 使用此方法,必须开启Spring Security的expression功能(use-expressions="true"):
<sec:http auto-config="true" use-expressions="true"></sec:http>
Spring Security Expression相关语法,请阅读: http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#el-access
参考文档 https://my.oschina.net/xiandafu/blog/143109
include include一个模板,如 :
<% include("/header.html"){} %>
若是想往子模板中传入参数,则能够后面跟一个json变量
<% include("/header.html",{'user':user,'id':user.id}){} %>
这样user,和id 能够在header.html被引用,并成为header.html的全局变量
(beetl1.2 也叫includeFileTemplate ,2.0仍然支持,但再也不文档里体现了)
layout 提供一个布局功能,每一个页面老是由必定布局,如页面头,菜单,页面脚,以及正文。 layout标签容许为正文指定一个布局,以下使用方式
content.html内容以下:
<%
//content.html内容以下: layout("/inc/layout.html"){ %> this is 正文 .......... <% } %>
layout.html 是布局文件,内容以下
javascript <% include("/inc/header.html"){} %> this is content:${layoutContent} this is footer:
运行content.html模板文件后,,正文文件的内容将被替换到layoutContent的地方,变成以下内容
`
javascript this is header this is content:this is 正文 ............ this is footer:
若是想往layout页面传入参数,则传入一个json变量,以下往layout.html页面传入一个用户登陆时间
```javascript
<% layout("/inc/header.html",{'date':user.loginDate,'title':"内容页面"}){ %> this is 正文 .......... <% } %>
若是layoutContent 命名有冲突,能够在layout第三个参数指定,如
javascript <% layout("/inc/header.html",{'date':user.loginDate,'title':"内容页面"},"myLayoutContent"){ %> this is 正文 .......... <% } %>
`
cache 能Cache标签的内容,并指定多长时间刷新,如
<% :cache('key2',10,false){ %> 内容体 <% } %>
须要指定三个参数
第三个表示是否强制刷新,false表示不,true表示强制刷新
Cache默认实现org.beetl.ext.tag.cache.SimpleCacheManager. 你能够设置你本身的Cache实现,经过调用CacheTag. cacheManager= new YourCacheImplementation();
能够在程序里调用以下方法手工删除Cache:
public void clearAll(); public void clearAll(String key); public void clearAll(String... keys);
includeJSP,能够在模板里包括一个jsp文件,如:
<%
includeJSP("/xxxx.jsp",{"key":"value"}){} %>
key value 都是字符串,将以parameter的形式提供给jsp,所以jsp能够经过request.getParameter("key")来获取参数
主要注意的是,这个标签并不是内置,须要手工注册一下
groupTemplate.registerTag("incdlueJSP",org.beetl.ext.jsp.IncludeJSPTag.class);
Beetl2.0目前只完成了解释引擎,使用解释引擎好处是能够适用于各类场景,性能测试代表,Beetl2.0引擎是Freemaker的4-6倍,跟最好的编译引擎性能相比,也相差只有30%百分点。为何Beetl能跑的如此之快呢,简单的说,有以下策略
相关文章
- 为何JSP会比Beetl慢 http://my.oschina.net/xiandafu/blog/475740
- Beetl 性能揭秘 2 :语言如何存取变量 http://my.oschina.net/xiandafu/blog/293167
- Beetl 性能揭秘 1 :如何输出一个整型变量 http://my.oschina.net/xiandafu/blog/284823
启动Eclipse
打开菜单栏按一下菜单路径依次打开
Help -> Install New Softwave… ->点击Add按钮弹出一个对话框
弹出的对话框中Name随意填写,如填写“beetl”,Location请填写
选中您要安装的Beetl Eclipse Plugin,按提示依次Next,直至Finish重启Eclipse便可.
使用说明:
测试用例一 https://github.com/javamonkey/ebm
测试用例二 http://git.oschina.net/kiang/teb
测试用例三 https://github.com/javamonkey/template-benchmark
Benchmark | version | Threads | Samples | Score | Score Error (99.9%) | Unit |
---|---|---|---|---|---|---|
Beetl | 2.7 | 1 | 50 | 42125.112914 | 3512.147131 | ops/s |
Freemarker | 2.3 | 1 | 50 | 13099.139808 | 339.612022 | ops/s |
Handlebars | 4.0 | 1 | 50 | 15808.044125 | 235.109622 | ops/s |
Mustache | 0.9 | 1 | 50 | 17961.391809 | 158.524109 | ops/s |
Rocker | 0.1 | 1 | 50 | 33631.370722 | 417.915637 | ops/s |
Thymeleaf | 3.0 | 1 | 50 | 4625.981276 | 67.313609 | ops/s |
注意
Score得分越高表示模板引擎每秒处理量越大性能越好
这个性能测试基本上结合了国内外的模板引擎,随着JDK版本的升级,JDK8提升了反射能力,减小了和Freemarker等模板引擎的性能差距,但Beetl依旧以3倍以上的性能优点秒杀Freemarker。
做者
助手
代码捐助者
文档校验
MVC框架若是加载不到模板,请先确认是否指定了正确的ResourceLoader。对于Spring Boot,使用的是ClassPathResourceLoaer,加载位于templates目录下的模板
对于其余WEB应用,内部使用的是FileResourceLoader,模板根目录位于web根目录。
Spring常见模板加载问题有可能以下缘由
<property name="prefix" value="/WEB-INF/view/"></property>
能够指定模板根目录
<bean name="beetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"> <property name="root" value="/WEB-INF/beetl.properties"/>
spring 视图名使用了相对路径,错误
return "userDetail.btl"
应该使用以下
return "/user/user.btl"
Spring Boot 自定义模板根目录
若是模板不在resources/templates目录下,好比在resouces/pages/views下,应该用以下方式初始化
ClasspathResourceLoader cploder = new ClasspathResourceLoader(BeetlTemplateConfig.class.getClassLoader(), "pages/views"); beetlGroupUtilConfiguration.setResourceLoader(cploder);
若是以上办法若是还不行,请尝试调试ResourceLoader的exist的方法,找到加载模板不成功缘由
这种现象主要出如今idea +maven的工程里,由于idea默认状况下不会同步模板文件到target某,所以即便你修改了模板,beetl也看不到变化。解决办法能够参考 渔泯小镇
http://bbs.ibeetl.com/bbs/bbs/topic/612-1.html
若是是其余环境出现这个问题,请确认修改的模板是否同步到目标环境里
Beetl默认使用了了以下引擎
ENGINE=org.beetl.core.engine.FastRuntimeEngine
这个引擎会假设同一个模板里的同一个全局变量应该类型惟一,若是你的模板是公共模板,类型不同,能够在模板顶部使用dynamic,好比
<% directive dynamic xxx %>
若是你的模板这种状况不少,建议更换成默认引擎配置
ENGINE=org.beetl.core.engine.DefaultTemplateEngine
还有种状况是在Spring Boot 下出现,参考下一节
请使用最新的Beetl版本,使用Starter或者参考Spring Boot集成一章集成Spring Boot。这是Spring Boot dev模式引发的问题