#默认配置:
##- 配置引擎实现类,默认即可.
ENGINE=org.beetl.core.engine.FastRuntimeEngine
##- 指定了占位符号,默认是${ },也可以指定为其他占位符。
DELIMITER_PLACEHOLDER_START=${
DELIMITER_PLACEHOLDER_END=}
##- 指定了语句的定界符号,默认是<% %>,也可以指定为其他定界符号
DELIMITER_STATEMENT_START=<%
DELIMITER_STATEMENT_END=%>
##- 指定IO输出模式,默认是FALSE,即通常的字符输出,在考虑高性能情况下,可以设置成true。详细请参考高级用法
DIRECT_BYTE_OUTPUT = FALSE
##- 指定了支持HTML标签,且符号为#,默认配置下,模板引擎识别<#tag ></#tag>这样的类似html标签,并能调用相应的标签函数或者模板文件。你也可以指定别的符号,如bg: 则识别<bg:
HTML_TAG_SUPPORT = true
HTML_TAG_FLAG = #
##- 指定如果标签属性有var,则认为是需要绑定变量给模板的标签函数
HTML_TAG_BINDING_ATTRIBUTE = var
##- 指定允许本地Class直接调用
NATIVE_CALL = TRUE
##- 指定模板字符集是UTF-8
TEMPLATE_CHARSET = UTF-8
##- 指定异常的解析类,默认是ConsoleErrorHandler,他将在render发生异常的时候在后台打印出错误信息(System.out)。
ERROR_HANDLER = org.beetl.core.ConsoleErrorHandler
##- 指定了本地Class调用的安全策略
NATIVE_SECUARTY_MANAGER= org.beetl.core.DefaultNativeSecurityManager
##- 配置了是否进行严格MVC,通常情况下,此处设置为false.
MVC_STRICT = FALSE
#资源配置,resource后的属性只限于特定ResourceLoader
##- 指定了默认使用的模板资源加载器,注意,在beetl与其他MVC框架集成的时候,模板加载器不一定根据这个配置,比如spring,他的RESOURCE_LOADER以spring的配置为准
RESOURCE_LOADER=org.beetl.core.resource.ClasspathResourceLoader
#classpath 根路径
##- 配置了模板资源加载器的一些属性,如设置根路径为/,即Classpath的顶级路径,并且总是检测模板是否更改
RESOURCE.root= /
#是否检测文件变化,开发用true合适,但线上要改为false
RESOURCE.autoCheck= true
#自定义脚本方法文件的Root目录和后缀
##- 配置了自定义的方法所在的目录以及文件名后缀。beetl既支持通过java类定义方法,也支持通过模板文件来定义方法
RESOURCE.functionRoot = functions
RESOURCE.functionSuffix = html
#自定义标签文件Root目录和后缀
##- 配置了自定义的html标签所在的目录以及文件名后缀。beetl既支持通过java类定义标签,也支持通过模板文件来定义标签
RESOURCE.tagRoot = htmltag
RESOURCE.tagSuffix = tag
##### 扩展 ##############
# 内置的方法
##- 注册了一个date方法,其实现类是org.beetl.ext.fn.DateFunction
FN.date = org.beetl.ext.fn.DateFunction
#......
#内置的功能包
##- 注册了一个方法包strutil,其实现类org.beetl.ext.fn.StringUtil,此类的每个public方法都将注册为beetl的方法
FNP.strutil = org.beetl.ext.fn.StringUtil
#......
#内置的默认格式化函数
##- 注册了一个日期格式化函数
FTC.java.util.Date = org.beetl.ext.format.DateFormat
#.....
# 标签类
##- 注册了一个include标签函数
TAG.include= org.beetl.ext.tag.IncludeTag
LP.index是Beetl语法 :
@for(item in menus){
@if(itemLP.first{
<a lay-href="${item .url}"><i class="layui-icon ${item .icon}"></i> <cite>${item.name}</cite></a>
@}
@}
itemLP是Beetl隐含定义的变量,能在循环体内使用。其命名规范是item名称后加上LP,他提供了当前循环的信息
#item 根据循环对象 变化比如 如果为 xxx 则 为 xxxLP.index
itemLP.index #当前的索引,从1开始
itemLP.size #集合的长度
itemLP.first #是否是第一个
itemLP.last #是否是最后一个
itemLP.even #索引是否是偶数
itemLP.odd #索引是否是奇数
资源加载器是根据String值获取Resource实例的工场类,同时资源加载器还要负责响应模板引擎询问模板是否变化的调用。对于新手来说,无需考虑模板资源加载器如何实现,只需要根据自己场景选择系统提供的三类模板资源加载器即可
字符串模板加载器
在创建GroupTemplate过程中,如果传入的是StringTemplateResourceLoader,则允许通过调用gt.getTemplate(String template)来获取模板实例对象
文件资源模板加载器
模板资源是以文件形式管理的,集中放在某一个文件目录下(如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);
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);
Configuration cfg = Configuration.defaultConfiguration();
WebAppResourceLoader resourceLoader = new WebAppResourceLoader();
groupTemplate = new GroupTemplate(resourceLoader, cfg);
WebAppResourceLoader 假定 beetl.jar 是位于 WEB-INF/lib 目录下,因此,可以通过WebAppResourceLoader类的路径来推断出WebRoot路径从而指定模板根路径。所有线上环境一般都是如此,如果是开发环境或者其他环境不符合此假设,你需要调用resourceLoader.setRoot() 来指定模板更路径
<%
/*此处是一个定义临时变量*/
ar 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("list",service.getUserList());
//在模板里
<%
for(user in list){
%>
hello,${user.name};
<% } %>
//共享变量
//共享变量指在所有模板中都可以引用的变量,可通过groupTemplate.setSharedVars(Map 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);
%>
//模板其他内容:
<% }; %>
template.binding("list",service.getUserList());
template.binding("pageMap",service.getPage());
//在模板里
总共 ${list.~size}
<%
for(user in list){
%>
hello,${user.name};
<% } %>
当前页${pageMap['page']},总共${pageMap["total"]}
虚拟属性也是对象的属性,是虚拟的,非模型对象的真实属性,这样的好处是当模板需要额外的用于显示的属性的时候但又不想更改模型,便可以采用这种办法
如beetl内置的虚拟属性.~size 针对了数组以及集合类型。
${user.gender}
${user.~genderShowName}
~genderShowName 是虚拟属性,其内部实现根据boolean变量gender来显示性别
Beetl内置了少量实用函数,可以在Beetl任何地方调用。如下例子是调用date 函数,不传参数情况下,返回当前日期
注意函数名支持namespace方式,因此代码第3行调用的函数是strutil.length
<%
var date = date();
var len = strutil.length("cbd");
println("len="+len);
%>
定义beetl的方法非常容易,有三种方法:
Beetl中,如果要输出的模板变量为null,则beetl将不做输出,这点不同于JSP,JSP输出null,也不同于Freemarker,如果没有用!,它会报错.
模板中还有俩种情况会导致模板输出异常:
有时候模板变量并不存在(譬如子模板里)
模板变量为null,但输出的是此变量的一个属性,如${user.wife.name}
针对前俩种种情况,可以在变量引用后加上!以提醒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.name)}
//beetl将会调用user.getName()方法,如果发生异常,beetl将会忽略此异常,继续渲染
实例:
${user.count!”无结果”} //字符串常量,如
${user.count!false} // boolean常量
${user.count!(-1)} //负数,则类似减号,容易误用
${user.count!@User.DEFAULT_NUM} //class直接调用,如
${user.count!getDefault() } //方法调用
${user.count!user.maxCount } //属性引用
<% var date = date(); %>
Today is ${date,dateFormat="yyyy-MM-dd"}.
Today is ${date,dateFormat}
salary is ${salary,numberFormat="##.##"}
eetl针对日期和数字类型提供的默认的格式化函数,在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
内置include 标签:
<%
include("/inc/header.html"){}
%>
gt.registerTag(“simpleTag”, SimpleHtmlTag.class); <#simpleTag attr="abc"></#simpleTag>
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);
<#tag limit="3";value>
${value}
</#tag>
可以通过符号@来表明后面表达式调用是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);
%>
如果你需要扩展更多属性,你也可以配置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);
}
}
Beetl建议通过配置文件配置GroupTemplate,主要考虑到IDE插件未来可能会支持Beetl模板,模板的属性,和函数等如果能通过配置文件获取,将有助于IDE插件识别。 配置GroupTemplate有俩种方法
配置文件: 默认配置在/org/beetl/core/beetl-default.properties 里,Beetl首先加载此配置文件,然后再加载classpath里的beetl.properties,并用后者覆盖前者。配置文件通过Configuration类加载,因此加载完成后,也可以通过此类API来修改配置信息
通过调用GroupTemplate提供的方法来注册函数,格式化函数,标签函数等
配置文件分为三部分,第一部分是基本配置,在第一节讲到过。第二部分是资源类配置,可以在指定资源加载类,以及资源加载器的属性,如下
ESOURCE_LOADER=org.beetl.core.resource.ClasspathResourceLoader
#资源配置,resource后的属性只限于特定ResourceLoader
#classpath 根路径
RESOURCE.root= /
#是否检测文件变化
RESOURCE.autouCheck= true
现Function
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,包含了模板的上下文,主要提供了如下属性
指令格式为: DIRECTIVE 指令名 指令参数(可选)
Beetl目前支持安全输出指令,分别是```
DIRECTIVE SAFE_OUTPUT_OPEN ; 打开安全输出功能,此指令后的所有表达式都具有安全输出功能,
DIRECTIVE SAFE_OUTPUT_CLOSE ; 关闭安全输出功能。详情参考安全输出
DIRECTIVE DYNAMIC varName1,varName2 …指示后面的变量是动态类型,Beetl应该考虑为Object. 也可以省略后面的变量名,则表示模板里所有变量都是Object
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}
以不用写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'}
需要实现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目录下同名的标签文件作为模板来执行。类似普通模板一样
public class DeleteTag extends Tag{
@Override
public void render(){
// do nothing,just ignore body
ctx.byteWriter.write("被删除了,付费可以看")
}
}
可以为特定类注册一个虚拟属性,也可以为一些类注册虚拟属性
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";
}
}
}
});
如下是虚拟属性类的定义:
public interface VirtualClassAttribute{
public Object eval(Object o, String attributeName, Context ctx);
}
public interface VirtualAttributeEval extends VirtualClassAttribute{
public boolean isSupport(Class c, String attributeName);
}
如果模板资源来自其他地方,如数据库,或者混合了数据库和物理文件,或者模板是加密的,则需要自定义一个资源加载器。资源加载器需要实现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);
}
}
}