当前位置: 首页 > 工具软件 > Beetl > 使用案例 >

Beetl 模板

段哲圣
2023-12-01

1. 配置项

	#默认配置:
    ##- 配置引擎实现类,默认即可.
    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

2. LP (这么奇特的规则,不说怎么知道)

LP.index是Beetl语法 :

  @for(item in menus){
    @if(itemLP.first{
    	 <a lay-href="${item .url}"><i class="layui-icon ${item .icon}"></i>&emsp;<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      #索引是否是奇数

3. 模板资源加载器

资源加载器是根据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);
  • Classpath资源模板加载器
    模板资源是打包到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);

  • WebApp资源模板加载器
    WebAppResourceLoader 是用于web应用的资源模板加载器,默认根路径是WebRoot目录。也可以通过制定root属性来设置相对于WebRoot的的模板根路径,从安全角考虑,建议放到WEB-INF目录下
Configuration cfg = Configuration.defaultConfiguration();
WebAppResourceLoader resourceLoader = new WebAppResourceLoader();
groupTemplate = new GroupTemplate(resourceLoader, cfg);

WebAppResourceLoader 假定 beetl.jar 是位于 WEB-INF/lib 目录下,因此,可以通过WebAppResourceLoader类的路径来推断出WebRoot路径从而指定模板根路径。所有线上环境一般都是如此,如果是开发环境或者其他环境不符合此假设,你需要调用resourceLoader.setRoot() 来指定模板更路径

  • 自定义资源模板加载器
    有时候模板可能来自文件系统不同目录,或者模板一部分来自某个文件系统,另外一部分来自数据库,还有的情况模板可能是加密混淆的模板,此时需要自定义资源加载,继承ResouceLoader才能实现模板功能

5. 变量定义和使用

<%
/*此处是一个定义临时变量*/
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);
%>
//模板其他内容:

<% }; %>
 

6. 引用属性

template.binding("list",service.getUserList());
template.binding("pageMap",service.getPage());

//在模板里
总共 ${list.~size}
<%
for(user in list){
%>
hello,${user.name};
<% } %>

当前页${pageMap['page']},总共${pageMap["total"]}


7. 虚拟属性

虚拟属性也是对象的属性,是虚拟的,非模型对象的真实属性,这样的好处是当模板需要额外的用于显示的属性的时候但又不想更改模型,便可以采用这种办法

如beetl内置的虚拟属性.~size 针对了数组以及集合类型。

${user.gender}
${user.~genderShowName}

~genderShowName 是虚拟属性,其内部实现根据boolean变量gender来显示性别

8. 函数调用

Beetl内置了少量实用函数,可以在Beetl任何地方调用。如下例子是调用date 函数,不传参数情况下,返回当前日期
注意函数名支持namespace方式,因此代码第3行调用的函数是strutil.length

<%
var date = date();
var len = strutil.length("cbd");
println("len="+len);
%>

定义beetl的方法非常容易,有三种方法:

  • 实现Function类的call方法,并添加到配置文件里,或者显示的通过代码注册registerFunction(name,yourFunction)
  • 可以直接调用registerFunctionPackage(namespace,yourJavaObject),这时候yourJavaObject里的所有public方法都将注册为Beetl方法,方法名是namespace+”.”+方法名
  • 可以直接写模板文件并且以html作为后缀,放到root/functions目录下,这样此模板文件自动注册为一个函数,其函数名是该模板文件名。

9. Beetl内置函数

  • date 返回一个java.util.Date类型的变量,如 date() 返回一个当前时间(对应java的java.util.Date); ${date( “2011-1-1” , “yyyy-MM-dd” )} 返回指定日期
  • print 打印一个对象 print(user.name);
  • println 打印一个对象以及回车换行符号,回车换号符号使用的是模板本身的,而不是本地系统的.如果仅仅打印一个换行符,则直接调用println() 即可
  • nvl 函数nvl,如果对象为null,则返回第二个参数,否则,返回自己 nvl(user,”不存在”)
  • isEmpty 判断变量或者表达式是否为空,变量不存在,变量为null,变量是空字符串,变量是空集合,变量是空数组,此函数都将返回true
  • isNotEmpty 同上,判断对象是否不为空
  • has 变量名为参数,判断是否存在此全局变量,如 has(userList),类似于1.x版本的exist(“userList”),但不需要输入引号了
  • assert 如果表达式为false,则抛出异常
  • trim 截取数字或者日期,返回字符,如trim(12.456,2)返回”12.45”,trim(date,’yyyyy’)返回”2017”
  • trunc 截取数字,保留指定的小数位,如trunc(12.456,2) 输出是12.45.不推荐使用,因为处理float有问题,兼容原因保留了
  • decode 一个简化的if else 结构,如 decode(a,1,”a=1”,2,”a=2”,”不知道了”)},如果a是1,这decode输出”a=1”,如果a是2,则输出”a==2”, 如果是其他值,则输出”不知道了”
  • debug 在控制台输出debug指定的对象以及所在模板文件以及模板中的行数,如debug(1),则输出1 [在3行@/org/beetl/core/lab/hello.txt],也可以输出多个,如debug(“hi”,a),则输出hi,a=123,[在3行@/org/beetl/core/lab/hello.txt]
  • parseInt 将数字或者字符解析为整形 如 parseInt(“123”);
  • parseLong 将数字或者字符解析为长整形,parseInt(123.12);
  • parseDouble 将数字或者字符解析为浮点类型 如parseDouble(“1.23”)
  • range 接收三个参数,初始值,结束值,还有步增(可以不需要,则默认为1),返回一个Iterator,常用于循环中,如for(var i in range(1,5)) {print(i)},将依次打印1234.
  • flush 强制io输出。
  • json,将对象转成json字符串,如 var data = json(userList) 可以跟一个序列化规则 如,var data = json(userList,”[*].id:i”),具体参考 https://git.oschina.net/xiandafu/beetl-json
  • pageCtx ,仅仅在web开发中,设置一个变量,然后可以在页面渲染过程中,调用此api获取,如pageCtx(“title”,”用户添加页面”),在其后任何地方,可以pageCtx(“title”) 获取该变量
  • type.new 创建一个对象实例,如 var user = type.new(“com.xx.User”); 如果配置了IMPORT_PACKAGE,则可以省略包名,type.new(“User”)
  • type.name 返回一个实例的名字,var userClassName = type.name(user),返回”User”
  • global 返回一个全局变量值,参数是一个字符串,如 var user = global(“user_”+i);
  • cookie 返回指定的cookie对象 ,如var userCook = cookie(“user”),allCookies = cookie();

10. 安全输出

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 }     //属性引用       

11. 格式化

<% 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

12. 标签函数

允许处理模板文件里的一块内容,功能等于同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"){}
%>

13. HTML标签

gt.registerTag(“simpleTag”, SimpleHtmlTag.class); <#simpleTag attr="abc"></#simpleTag>

14. 绑定变量的HTML标签

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>

15. 直接调用java方法和属性

可以通过符号@来表明后面表达式调用是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);
%>

16. Web提供的全局变量

  • request 中的所有attribute.在模板中可以直接通过attribute name 来引用,如在controller层 request.setAttribute(“user”,user),则在模板中可以直接用${user.name} .
  • session 提供了session会话,模板通过session[“name”],或者session.name 引用session里的变量.注意,session并非serlvet里的标准session对象。参考servlet来获取HTTPSession。
  • request 标准的HTTPServletRequest,可以在模板里引用request属性(getter),如${request.requestURL}。
  • parameter 读取用户提交的参数。如${parameter.userId} (仅仅2.2.7以上版本支持)
  • ctxPath Web应用ContextPath
  • servlet 是WebVariable的实例,包含了HTTPSession,HTTPServletRequest,HTTPServletResponse.三个属性,模板中可以通过request,response,session 来引用,如 ${servlet.request.requestURL};
    所有的GroupTemplate的共享变量pageCtx是一个内置方法 ,仅仅在web开发中,用于设置一个变量,然后可以在页面渲染过程中,调用此api获取,如pageCtx(“title”,”用户添加页面”),在其后任何地方,可以pageCtx(“title”) 获取该变量。(仅仅2.2.7以上版本支持)
    你可以在模板任何地方访问这些变量

如果你需要扩展更多属性,你也可以配置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);
        }
}

配置GroupTemplate

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

17. 自定义方法

现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,包含了模板的上下文,主要提供了如下属性

  • byteWriter 输出流
  • template 模板本身
  • gt GroupTemplate
  • globalVar 该模板对应的全局变量
  • byteOutputMode 模板的输出模式,是字节还是字符
  • safeOutput 模板当前是否处于安全输出模式

18. 指令

指令格式为: DIRECTIVE 指令名 指令参数(可选) 
Beetl目前支持安全输出指令,分别是```
DIRECTIVE SAFE_OUTPUT_OPEN ; 打开安全输出功能,此指令后的所有表达式都具有安全输出功能,
DIRECTIVE SAFE_OUTPUT_CLOSE ; 关闭安全输出功能。详情参考安全输出
DIRECTIVE DYNAMIC varName1,varName2 …指示后面的变量是动态类型,Beetl应该考虑为Object. 也可以省略后面的变量名,则表示模板里所有变量都是Object

19. Beetl小工具

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}

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

以不用写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'}

21. 自定义格式化函数

需要实现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);

22. 自定义标签

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

  • 标签函数
public class DeleteTag extends Tag{
        @Override
        public void render(){
                // do nothing,just ignore body
                ctx.byteWriter.write("被删除了,付费可以看")
        }
}
  • args 传入标签的参数
  • gt GroupTemplate
  • ctx Context
  • bw 当前的输出流

23. 自定义虚拟属性

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

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);
  }

24. 自定义资源加载器

如果模板资源来自其他地方,如数据库,或者混合了数据库和物理文件,或者模板是加密的,则需要自定义一个资源加载器。资源加载器需要实现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);
                }
        }
}
 类似资料: