Beetl中文文档


Beetl2.7.18中文文档

Beetl作者:李家智 <xiandafu@126.com>

1. 什么是Beetl

Beetl目前版本是2.7.21,相对于其他java模板引擎,具有功能齐全,语法直观,性能超高,以及编写的模板容易维护等特点。使得开发和维护模板有很好的体验。是新一代的模板引擎。总得来说,它的特性如下:

  • 功能完备:作为主流模板引擎,Beetl具有相当多的功能和其他模板引擎不具备的功能。适用于各种应用场景,从对响应速度有很高要求的大网站到功能繁多的CMS管理系统都适合。Beetl本身还具有很多独特功能来完成模板编写和维护,这是其他模板引擎所不具有的。
  • 非常简单:类似Javascript语法和习俗,只要半小时就能通过半学半猜完全掌握用法。拒绝其他模板引擎那种非人性化的语法和习俗。同时也能支持html 标签,使得开发CMS系统比较容易
  • 超高的性能:Beetl 远超过主流java模板引擎性能(引擎性能5-6倍与freemaker,2倍于JSP。参考附录),而且消耗较低的CPU。
  • 易于整合:Beetl能很容易的与各种web框架整合,如Spring MVC,JFinal,Struts,Nutz,Jodd,Servlet等。
  • 支持模板单独开发和测试,即在MVC架构中,即使没有M和C部分,也能开发和测试模板。
  • 扩展和个性化:Beetl支持自定义方法,格式化函数,虚拟属性,标签,和HTML标签. 同时Beetl也支持自定义占位符和控制语句起始符号也支持使用者完全可以打造适合自己的工具包。

#### 关于性能

通过与主流模板引擎Freemarker,Vecloity以及JSP对比,Beetl6倍于Freemarker,2倍于JSP。这是因为宏观上,通过了优化的渲染引擎,IO的二进制输出,字节码属性访问增强,微观上,通过一维数组保存上下文Context,静态文本合并处理,通过重复使用字节数组来防止java频繁的创建和销毁数组,还使用模板缓存,运行时优化等方法。详情参考附录

#### 独特功能

Beetl有些功能是发展了10多年的模板引擎所不具备的,这些功能非常利于模板的开发和维护,如下

  1. 自定义占位符和控制语句起始符号,这有利于减小模板语法对模板的倾入性,比如在html模板中,如果定义控制语句符号是<!--:-->,那么,大部分模板文件都能通过浏览器打开。有的使用者仅仅采用了单个符号@ (或者单个符号“”)以及回车换号作为控制语句起始符号,这又能提高开发效率
  2. 可单独测试的模板。无需真正的控制层和模型层,Beetl的模板就可以单独开发和测试
  3. 同时支持较为松散的MVC和严格的MVC,如果在模板语言里嵌入计算表达式,复杂条件表达式,以及函数调用有干涉业务逻辑嫌疑,你可以禁止使用这些语法。
  4. 强大的安全输出,通过安全输出符号!,能在模板变量,变量属性引用,for循环,占位符输出,try-catch中等各个地方提供安全输出,保证渲染正常。
  5. 模板变量:运行将模板的某一部分输出像js那样赋值给一个变量,稍后再处理。利用模板变量能完成非常复杂的页面布局(简单的布局可使用include,layout标签函数)
  6. 类型推测,能在运行的时候推测模板变量类型,从而优化性能,也可以通过注解的方法显示的说明模板变量属性(这是非必须的,但有助于IDE自动提示功能)
  7. 可插拔的设计,错误信息提示,模板引擎缓存机制,模板资源管理,本地调用的安全管理器,严格MVC限制,模板引擎本身都有默认的实现,但又完全可以自定义以适合特定需求
  8. 增强的语法,如for-elsefor, select-case,安全输出符号!,省略的三元表达式 等,这些语法特别适合模板开发
  9. 局部渲染技术,结合现在js的ajax技术。
  10. 性能超高,具有最快的模板解释引擎,同时,又有较低的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

    邮件:xiandafu@126.com

    Beetl社区:bbs.ibeetl.com

    源码主页:https://github.com/javamonkey/beetl2.0

    在线体验和代码分享 http://ibeetl.com/beetlonline/

2. 基本用法

2.1. 安装

如果使用maven,请使用如下坐标

<dependency>  
        <groupId>com.ibeetl</groupId>
        <artifactId>beetl</artifactId>
        <version>2.7.21</version>
</dependency>  

如果非maven工程,直接下载http://git.oschina.net/xiandafu/beetl2.0/attach_files

2.2. 从GroupTemplate开始

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种,分别是

  • StringTemplateResourceLoader:字符串模板加载器,用于加载字符串模板,如本例所示
  • FileResourceLoader:文件模板加载器,需要一个根目录作为参数构造,传入getTemplate方法的String是模板文件相对于Root目录的相对路径
  • ClasspathResourceLoader:文件模板加载器,模板文件位于Classpath里
  • WebAppResourceLoader:用于webapp集成,假定模板根目录就是WebRoot目录,参考web集成章
  • MapResourceLoader : 可以动态存入模板
  • CompositeResourceLoader 混合使用多种加载方式

代码第5行将变量name传入模板里,其值是“Beetl”。 代码第6行是渲染模板,得到输出,template提供了多种获得渲染输出的方法,如下

  • tempalte.render() 返回渲染结果,如本例所示
  • template.renderTo(Writer) 渲染结果输出到Writer里
  • template.renderTo(OutputStream ) 渲染结果输出到OutputStream里
  1. 关于如何使用模板资源加载器,请参考下一节
  2. 如何对模板进行配置,请参考下一节
  3. 如果不想写代码直接体验Beetl,可以使用http://ibeetl.com/beetlonline/

2.3. 模板基础配置

Beetl提供不但功能齐全,而且还有很多独特功能,通过简单的配置文件,就可以定义众多的功能,默认情况下,Configuration类总是会先加载默认的配置文件(位于/org/beetl/core/beetl-default.properties,作为新手,通常只需要关注3,4,5,6行定界符的配置,以及12行模板字符集的配置就可以了,其他配置会在后面章节陆续提到)下,其内容片断如下:

#默认配置
ENGINE=org.beetl.core.engine.FastRuntimeEngine  
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  

第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的配置属性,如果有重复,用后者代替前者的配置

properties #自定义配置 DELIMITER_STATEMENT_START=<!--: DELIMITER_STATEMENT_END=-->

2.4.0 新功能:beetl 支持通过模板本生来完成函数,即模板函数,或者通过模板来实现HTML标签(而不用写java代码),可以beetl.properties为这种应用设置的不同的语句定界符来跟常规模板做区分,如下

properties FUNCTION_TAG_LIMITER=<% ; %>

分号分割开,如果配置文件没有FUNCTIONTAGLIMITER=,则模板函数,html标签使用同DELIMITERSTATEMENTSTARTDELIMITERSTATEMENTEND

2.4. 模板资源加载器

资源加载器是根据String值获取Resource实例的工场类,同时资源加载器还要负责响应模板引擎询问模板是否变化的调用。对于新手来说,无需考虑模板资源加载器如何实现,只需要根据自己场景选择系统提供的三类模板资源加载器即可

2.4.1. 字符串模板加载器

在创建GroupTemplate过程中,如果传入的是StringTemplateResourceLoader,则允许通过调用gt.getTemplate(String template)来获取模板实例对象,如2.1所示

2.4.2. 文件资源模板加载器

更通常情况下,模板资源是以文件形式管理的,集中放在某一个文件目录下(如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来加载模板

2.4.3. 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);  

第1行代码指定了模板根目录,即搜索模板的时候从根目录开始,如果new ClasspathResourceLoader("template/"),则表示搜索template下的模板。此处用空构造函数,表示搜索路径是根路径,且字符集默认字符集UTF-8.

第4行通过模板的相对路径org/beetl/sample/s01/hello.txt来加载模板

2.4.4. WebApp资源模板加载器

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() 来指定模板更路径

2.4.5. 自定义资源模板加载器

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

2.5. 定界符与占位符号

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 带来的这种非人性语法

定界符和占位符 通常还有别的选择,如下定界符

  • @ 和回车换行 (此时,模板配置DELIMITERSTATEMENTEND= 或者 DELIMITERSTATEMENTEND=null 都可以)
  • #: 和回车换行
  • <!--: 和 -->
  • <!--# 和 -->
  • <? 和 ?>

占位符--#{ }-##

你也可以与团队达成一致意见来选择团队喜爱的定界符号和占位符号。

定界符号里是表达式,如果表达式跟定界符或者占位符有冲突,可以在用 “\” 符号,如

@for(user in users){
email is ${user.name}\@163.com  
@}
${[1,2,3]} //输出一个json列表
${ {key:1,value:2 \}  } //输出一个json map,} 需要加上\

2.6. 注释

Beetl语法类似js语法,所以注释上也同js一样: 单行注释采用//

多行注视采用/**/

<%  
/*此处是一个定义变量*/
var a = 3; //定义一个变量.

/* 以下内容都将被注释
%>

<% */ %>  
{gfm-js-extract-pre-11}javascript
<%  
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;  
%>
{gfm-js-extract-pre-12}javascript
template.binding("list",service.getUserList());

//在模板里
<%  
for(user in list){  
%>
hello,${user.name};  
<% } %>  
{gfm-js-extract-pre-13}java
//.....
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);  
{gfm-js-extract-pre-14}javascript
//t1.txt
hi,${name}  
//t2.txt
hello,${name}  
{gfm-js-extract-pre-15}javascript
<%  
var content = {  
        var c = "1234";
        print(c);
%>
模板其他内容:

<% }; %>  
{gfm-js-extract-pre-16}javascript
template.binding("list",service.getUserList());  
template.binding("pageMap",service.getPage());

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

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

{gfm-js-extract-pre-17}javascript
<%  
var user = ....  
user.name="joelli";  
user.friends[0] = getNewUser();  
user.map["name"] = "joelli";  
%>
{gfm-js-extract-pre-18}javascript
<%  
var a = 1;  
var b = "hi";  
var c = a++;  
var d = a+100.232;  
var e = (d+12)*a;  
var f = 122228833330322.1112h  
%>
{gfm-js-extract-pre-19}javascript
<%  
var a = 1;  
var b="good";  
var c = null;

if(a!=1&&b=="good"&&c==null){  
        ......
}
%>
{gfm-js-extract-pre-20}javascript
<%  
 var  a = 1 ;
%>
${a==1?"ok":''}
${a==1?"ok"}
{gfm-js-extract-pre-21}javascript
<%  
for(user in userList){  
        print(userLP.index);
        print(user.name);
}
%>
{gfm-js-extract-pre-22}javascript
<%  
for(entry in map){  
        var key = entry.key;
        var value = entry.value;
        print(value.name);
}
%>
{gfm-js-extract-pre-23}javascript
<%  
var a = [1,2,3];  
for(var i=0;i<a.~size;i++){  
        print(a[i]);
}
%>
{gfm-js-extract-pre-24}javascript
<%  
var i = 0;  
while(i<5){  
        print(i);
        i++;
}
%>
{gfm-js-extract-pre-25}javascript
<%  
var list = [];  
for(item in list){

}elsefor{
        print("未有记录");
}
%>
{gfm-js-extract-pre-26}javascript
<%  
var a =true;  
var b = 1;  
if(a&&b==1){

}else if(a){

}else{

}
%>
{gfm-js-extract-pre-27}javascript
<%  
var b = 1;  
switch(b){  
        case 0:
                print("it's 0");
                break;
        case 1:
                print("it's 1");
                break;
        default:
                print("error");
}
%>
{gfm-js-extract-pre-28}javascript
<%  
var b = 1;  
select(b){  
        case 0,1:
                print("it's small int");
        case 2,3:
                print("it's big int");
        default:
                print("error");
}
%>
{gfm-js-extract-pre-29}javascript
<%  
select {  
        case boolExp,orBoolExp2:
                doSomething();
}
%>
{gfm-js-extract-pre-30}javascript
<%  
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");
}
%>
{gfm-js-extract-pre-31}javascript
<%  
try{  
        callOtherSystemView()
}catch(error){
        print("暂时无数据");
}
%>
{gfm-js-extract-pre-32}javascript
${user.gender}
${user.~genderShowName}
{gfm-js-extract-pre-33}javascript
<%  
var date = date();  
var len = strutil.length("cbd");  
println("len="+len);  
%>
{gfm-js-extract-pre-34}javascript
<%  
var k = user.name!'N/A'+user.age!;  
%>
<%  
${k}
%>
{gfm-js-extract-pre-35}javascript
<%  
DIRECTIVE SAFE_OUTPUT_OPEN;  
%>
${user.wife.name}
模板其他内容,均能安全输出……
<%  
//关闭安全输出。
DIRECTIVE SAFE_OUTPUT_CLOSE;  
%>
{gfm-js-extract-pre-36}javascript
<%  
var list = null;  
for(item in list!){

}elsefor{
        print("no data");
}
%>
{gfm-js-extract-pre-37}javascript
<%  
if(has(flag)){  
        print("flag变量存在,可以访问")
}
%>
{gfm-js-extract-pre-38}javascript
<%  
if(has(flag)&&flag==0){  
        //code
}
%>
{gfm-js-extract-pre-39}javascript
<%  
if(flag!0==0){  
        //code
}
%>
{gfm-js-extract-pre-40}javascript
<% var date = date(); %>  
Today is ${date,dateFormat="yyyy-MM-dd"}.  
Today is ${date,dateFormat}  
salary is ${salary,numberFormat="##.##"}  
{gfm-js-extract-pre-41}javascript
${date,“yyyy-MM-dd”}
{gfm-js-extract-pre-42}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  
{gfm-js-extract-pre-43}javascript
<%  
layout("/inc/layout.html",{title:'主题'}){  
%>
Hello,this is main part  
<% } %>  
{gfm-js-extract-pre-44}javascript
title is ${title}  
body content ${layoutContent}  
footer  
{gfm-js-extract-pre-45}javascript
<%  
include("/inc/header.html"){}  
%>
{gfm-js-extract-pre-46}java
public class CompressTag extends Tag{  
        @Override
        public void render(){
                BodyContent  content = getBodyContent();
                String content = content.getBody();
                String zip = compress(conent);
                ctx.byteWriter.write(zip);
        }
}
{gfm-js-extract-pre-47}xml
<#footer style=”simple”/>  
<#richeditor id=”rid” path="${ctxPath}/upload" name=”rname”  maxlength=”${maxlength}”> ${html} …其他模板内容   </#richdeitor>  
<#html:input  id=’aaaa’ />  
{gfm-js-extract-pre-48}javascript
<% if(style==’simple’){ %>  
 请联系我 ${session.user.name}
<% }else{ %>  
请联系我 ${session.user.name},phone:${session.user.phone}
<% } %>  
{gfm-js-extract-pre-49}java
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){

                }
        }
}
{gfm-js-extract-pre-50}xml
<#simpleTag attr="abc"></#simpleTag>  
{gfm-js-extract-pre-51}java
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);
{gfm-js-extract-pre-52}xml
<#tag limit="3";value>  
        ${value}
</#tag>  
{gfm-js-extract-pre-53}xml
<#tag limit="3" var="value">  
        ${value}
</#tag>  
{gfm-js-extract-pre-54}javascript
${@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);  
%>
{gfm-js-extract-pre-55}javascript
<% DIRECTIVE DYNAMIC idList;  
for(value in idList) .....  
{gfm-js-extract-pre-56}javascript
<%  
/**
*@type (List<User> idList,User user)
*/
for(value in idList) .....  
{gfm-js-extract-pre-57}javascript
<%  
var a = 1;  
var b = a/0;  
%>
{gfm-js-extract-pre-58}javascript
>>DIV_ZERO_ERROR:0 位于3行 资源:/org/beetl/sample/s0125/error1.txt
1|<%  
2|var a = 1;  
3|var b = a/0;  
4| %>  
{gfm-js-extract-pre-59}javascript
<%  
var a = 1;  
var b = a  
var c = a+2;  
%>
{gfm-js-extract-pre-60}javascript
>>缺少符号(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| %>  
{gfm-js-extract-pre-61}java
String template = "var a=1,c=2+1;";  
Map result = executeAndReturnRootScopeVars(template);  
System.out.println(result);  
//输出结果是{c=3, a=1}
{gfm-js-extract-pre-62}properties
RESOURCE_LOADER=org.beetl.core.resource.ClasspathResourceLoader  
#资源配置,resource后的属性只限于特定ResourceLoader
#classpath 根路径
RESOURCE.root= /  
#是否检测文件变化
RESOURCE.autouCheck= true  
{gfm-js-extract-pre-63}properties
#####  扩展 ##############
## 内置的方法
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

{gfm-js-extract-pre-64}java
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 "";
        }
}
{gfm-js-extract-pre-65}java
public class util{  
        public String print(Object a, Context ctx){
                //balabala...
        }
}
{gfm-js-extract-pre-66}javascript
<%  
//para0,para1 由函数调用传入
var current = para0,total = para1,style=para2!'simple'  
%>
当前页面 ${current},总共${total}
{gfm-js-extract-pre-67}javascript
<%  
page(current,total);  
%>
{gfm-js-extract-pre-68}javascript
<%  
return date();  
%>
{gfm-js-extract-pre-69}javascript
hello time is ${now(),'yyyy-MM-dd'}  
{gfm-js-extract-pre-70}java
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");
                }
        }
}
{gfm-js-extract-pre-71}java
public abstract Object format(Object data,String pattern,Context ctx);  
{gfm-js-extract-pre-72}java
public class DeleteTag extends Tag{  
        @Override
        public void render(){
                // do nothing,just ignore body
                ctx.byteWriter.write("被删除了,付费可以看")
        }
}
{gfm-js-extract-pre-73}java
public class XianDeDantengTag extends Tag{  
        @Override
        public void render(){
                doBodyRender();
        }
}
{gfm-js-extract-pre-74}java
public class CompressTag extends Tag{  
        @Override
        public void render(){
                BodyContent  content = getBodyContent();
                String content = content.getBody();
                String zip = compress(conent);
                ctx.byteWriter.write(zip);
        }
}
{gfm-js-extract-pre-75}java
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;
}
{gfm-js-extract-pre-76}java
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);
}
{gfm-js-extract-pre-77}java
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;
        }
}
{gfm-js-extract-pre-78}java
@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("/"));
        }
}
{gfm-js-extract-pre-79}java
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);
                }
        }
}
{gfm-js-extract-pre-80}java
public abstract class Resource{

        /**
         * 打开一个新的Reader
         *
         * @return
         */
        public abstract Reader openReader();

        /**
         * 检测资源是否改变
         *
         * @return
         */
        public abstract boolean isModified();
{gfm-js-extract-pre-81}java
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");  
{gfm-js-extract-pre-82}javascript
<%  
include("/xxx2.html"){}  
include("http:/xxx.html"){}  
%>
{gfm-js-extract-pre-83}javascript
<%  
include("db:1"){}  
%>
{gfm-js-extract-pre-84}java
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;
        }
}
{gfm-js-extract-pre-85}java
PlaceholderST.output = new PlaceholderST.Output(){  
        @Override
        public void write(Context ctx, Object value) throws IOException {
                //定制输出
                ctx.byteWriter.writeString("ok"+value!=null?value.toString:"");
        }
};
{gfm-js-extract-pre-86}javascript
<%  
for(var i=0;i<10000000;i++){  
        //其他代码
}
%>
{gfm-js-extract-pre-87}java
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);
        }

}
{gfm-js-extract-pre-88}java
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();
        }
}
{gfm-js-extract-pre-89}java
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;
        }
}
{gfm-js-extract-pre-90}java
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;
                }
        }
}
{gfm-js-extract-pre-91}properties
ENGINE=org.bee.tl.online.OnlineTemplateEngine  
{gfm-js-extract-pre-92}javascript
var a = 1;  
var b = date();  
var c = '2';  
return a+1;  
{gfm-js-extract-pre-93}properties
RESOURCE.root=/WEB-INF/views  
WEBAPP_EXT = com.park.oss.util.GlobalExt  
{gfm-js-extract-pre-94}java
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);
        }
}
{gfm-js-extract-pre-95}java
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;
        }
}
{gfm-js-extract-pre-96}java
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);
}
{gfm-js-extract-pre-97}xml
<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>  
{gfm-js-extract-pre-98}java
BeetlGroupUtilConfiguration config = (BeetlGroupUtilConfiguration) this.getApplicationContext().getBean("beetlConfig");  
GroupTemplate group = config.getGroupTemplate();  
{gfm-js-extract-pre-99}java
@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView index(HttpServletRequest req) {  
    ModelAndView view = new ModelAndView("/index");
    //total 是模板的全局变量,可以直接访问
    view.addObject("total",service.getCount());
    return view;
}
{gfm-js-extract-pre-100}xml
<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>  
{gfm-js-extract-pre-101}java
@Service
@Scope("prototype")
public class TestTag extends Tag {  
}
{gfm-js-extract-pre-102}xml
<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>  
{gfm-js-extract-pre-103}java
@Configuration
public class BeetlConf {

        @Value("${beetl.templatesPath}") String templatesPath;//模板跟目录 ,比如 "templates"
        @Bean(initMethod = "init", name = "beetlConfig")
        public BeetlGroupUtilConfiguration getBeetlGroupUtilConfiguration() {
                BeetlGroupUtilConfiguration beetlGroupUtilConfiguration = new BeetlGroupUtilConfiguration();
                try {
                        ClasspathResourceLoader cploder = new ClasspathResourceLoader(BeetlConf.class.getClassLoader(),templatesPath);
                        beetlGroupUtilConfiguration.setResourceLoader(cploder);
                        return beetlGroupUtilConfiguration;
                } catch (Exception e) {
                        throw new RuntimeException(e);
                }

        }

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

 }
{gfm-js-extract-pre-104}properties
restart.include.beetl=/beetl-[\\w]+\.jar  
restart.include.beetlsql=/beetlsql-[\\w]+\.jar  
{gfm-js-extract-pre-105}xml
<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>  
{gfm-js-extract-pre-106}java
public class MyAutomagicMadvocConfigurator extends AutomagicMadvocConfigurator {  
        public MyAutomagicMadvocConfigurator(){
                super();
                //不扫描beetl 里jar文件里的action和result,否则,会扫描StrutsResultSupport不相干的class
                this.rulesJars.exclude("**/*beetl*.jar");
        }
}
{gfm-js-extract-pre-107}java
public class MyWebApplication  extends WebApplication{  
        @Override
        protected void init(MadvocConfig madvocConfig, ServletContext servletContext) {
                //设置默认
                madvocConfig.setDefaultActionResult(BeetlActionResult.class);
        }
 }
{gfm-js-extract-pre-108}java
@MadvocAction
public class IndexAction {  
        @Out
        String value;
        @Action("/index.html")
        public String world() {
                value = "Hello World!";
                return "/ok.html";
        }
}
{gfm-js-extract-pre-109}java
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可以添加扩展函数,格式化函数,共享变量等,

    }
{gfm-js-extract-pre-110}java
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");
}
{gfm-js-extract-pre-111}properties
RESOURCE.root= /WEB-INF/template/  
{gfm-js-extract-pre-112}java
//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;
    }

}
{gfm-js-extract-pre-113}java
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);
        }

    }

}
{gfm-js-extract-pre-114}java
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 ;
        }
}
{gfm-js-extract-pre-115}java
@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;
}
{gfm-js-extract-pre-116}xml
<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>  
{gfm-js-extract-pre-117}javascript
<%  
directive dynamic obj  
%>

${obj.user.title}
${obj.user.name}
{gfm-js-extract-pre-118}xml
<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>  
{gfm-js-extract-pre-119}javascript
<#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片段结尾
}
%>
{gfm-js-extract-pre-120}javascript
render("/index.html#userTable");  
{gfm-js-extract-pre-121}javascript
<%  
<html>

</html>  
#ajax norender success: {
%>
<div id="success"> 操作成功  
</div>

<%  
}
%>

#ajax norender failure: {
%>
<div id="failure"> 操作失败  
</div>

<%  
}
%>
{gfm-js-extract-pre-122}properties
ERROR_HANDLER = org.beetl.ext.web.WebErrorHandler  
{gfm-js-extract-pre-123}xml
<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>  
{gfm-js-extract-pre-124}java
// 测试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)  
5.1.6. Spring security

下列三个函数只需以函数的方式定义在BeetlGroupUtilConfiguration的functions中即可,与spel函数一样的,函数名声明在functions中,可以更改

  • auth() 对应类: org.beetl.ext.spring.AuthenticationFunction 方法无参数 返回值: 返回当前安全上下文中的用户认证凭证Authentication实例 如果当前环境不存在Spring Security安全上下文,将返回null值
  • urlIf(\, \) 对应类: org.beetl.ext.spring.AccessUrlIfFunction 参数: url: 字符串表示的测试URL Path,不需要指定Context Path,缺省会直接返回true method: 字符串表示的访问方式, 默认为GET, 建议全大写 返回值: 测试当前登录用户是否能访问指定的URL Path, 返回true or false

    示例:

    javascript urlIf('/system/admin_update.do', 'POST'))

    如果当前环境不存在Spring Security安全上下文,将返回true 如果当前环境不存在用户认证凭证,作为匿名登录进行测试

  • expIf(\) 对应类: org.beetl.ext.spring.AccessExpressionIfFunction 参数: exp: Spring Security安全表达式,缺省会直接返回true 返回值: 测试当前登录用户是否满足指定的安全表达式,返回true or false 示例:

    javascript expIf('isAuthenticated()')

    如果当前环境不存在Spring Security安全上下文,将返回true 如果当前环境不存在用户认证凭证,作为匿名登录进行测试

    注意: 使用此方法,必须开启Spring Security的expression功能(use-expressions="true"):

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

5.1.7. shiro

参考文档 https://my.oschina.net/xiandafu/blog/143109

5.2. 内置格式化方法

5.3. 内置标签函数

  • include include一个模板,如 :

    ```javascript <% include("/header.html"){} %>

    ```

    如果想往子模板中传入参数,则可以后面跟一个json变量

    ```javascript <% include("/header.html",{'user':user,'id':user.id}){} %>

    ```

    这样user,和id 可以在header.html被引用,并成为header.html的全局变量

    (beetl1.2 也叫includeFileTemplate ,2.0仍然支持,但不再文档里体现了)

  • layout 提供一个布局功能,每个页面总是由一定布局,如页面头,菜单,页面脚,以及正文。 layout标签允许为正文指定一个布局,如下使用方式

    content.html内容如下:

    ```javascript <% //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标签的内容,并指定多长时间刷新,如

    ```javascript <% :cache('key2',10,false){ %> 内容体 <% } %>

    ```

    需要指定三个参数

    • 第一个是cache的Key值
    • 第二个是缓存存在的时间,秒为单位
    • 第三个表示是否强制刷新,false表示不,true表示强制刷新

      Cache默认实现org.beetl.ext.tag.cache.SimpleCacheManager. 你可以设置你自己的Cache实现,通过调用CacheTag. cacheManager= new YourCacheImplementation();

      可以在程序里调用如下方法手工删除Cache:

    java public void clearAll(); public void clearAll(String key); public void clearAll(String... keys);

  • includeJSP,可以在模板里包括一个jsp文件,如:

    ```javascript <% includeJSP("/xxxx.jsp",{"key":"value"}){} %>

    ```

    key value 都是字符串,将以parameter的形式提供给jsp,因此jsp可以通过request.getParameter("key")来获取参数

    主要注意的是,这个标签并非内置,需要手工注册一下

    java groupTemplate.registerTag("incdlueJSP",org.beetl.ext.jsp.IncludeJSPTag.class);

5.4. 性能优化的秘密

Beetl2.0目前只完成了解释引擎,使用解释引擎好处是可以适用于各种场景,性能测试表明,Beetl2.0引擎是Freemaker的4-6倍,跟最好的编译引擎性能相比,也相差只有30%百分点。为什么Beetl能跑的如此之快呢,简单的说,有如下策略

  • 优化IO输出,允许使用字节直接输出,模板中的静态文本事先转化为字节
  • encode优化,对于number类型,输出通常是.toString 转化成String,然后encode输出,这中间浪费了大量的资源,Beetl实现了encode,输出一步到位
  • Context 采用一维数组,语言里的Context通常采用Map实现,每次进入{} ,就新增一个child Map,尽管map很快,但不够快。也有其他模板语言采用二位数组提高性能,Beetl是通过固定大小一维数组来维护模板的Context,因此访问更快,也避免了Map和二维素组的频繁创建。其实除了此处,beetl很多地方都不采用Map来维护key-value, 而都采用数组索引,以追求性能极限
  • 字节码访问属性,通过反射获取性能比较慢,就算JVM有优化,但优化效果也不确定。Beetl通过字节码生成了属性访问类,从而将属性访问速度提高了一个数量级
  • 类型推测:Beetl 是强制类型的,因此预先知道类型,可以对模板做一些优化而省去了动态判断类型的时间
  • 使用数组Buffer,避免频繁创建和销毁数组
  • 编译引擎将模板编译成类,会产生大量的类,虚拟机很难对这些做优化。而解释引擎只有几十个固定的类,虚拟机容易优化

#### 相关文章

5.5. Eclipse 插件

  • 启动Eclipse

  • 打开菜单栏按一下菜单路径依次打开

    Help -> Install New Softwave… ->点击Add按钮弹出一个对话框

  • 弹出的对话框中Name随意填写,如填写“beetl”,Location请填写

    http://ibeetl.com/eclipse/

选中您要安装的Beetl Eclipse Plugin,按提示依次Next,直至Finish重启Eclipse即可.

使用说明:

  1. 工程属性里有个beetl属性,可以指定定界符号等,默认是<%%> ${}。也可以指定模板根目录(可选,不必手工填写,在模板单击定位里会提示你选择)
  2. ctrl-2 定位到下一个beetl 块
  3. ctrl-3 定位到上一个beetl块
  4. ctrl-4 将普通文件以beetl editor方式打开,并保持同步编辑
  5. ctrl-5 静态文本全部折叠和打开静态文本折叠
  6. 可以ctrl+单击字符串定位到字符串对应的模板文件,第一次使用的时候,需要选择模板根目录,随后,也可以在project属性的beetl配置里配置模板根目录
  7. alt-/ 进行上下文提示。也可以键入此快速输入定界符号和占位符号
  8. alt-shift-p 从{ 快速移动到 匹配的},或者反之亦然。如果只单击{ 则会框选住匹配的} 而光标不移动
  9. 选中任何id,都能全文框选住同样的id。
  10. ctrl-/ 单行注释,或者取消注释
  11. 通常eclipse具有的快捷操作方式,beetl仍然予以保留不变
  12. 具备一定的错误提示,目前只提示第一个发现的错误。
  13. 双击{ } 可以选中之间的内容

5.6. 性能测试对比

测试用例一 https://github.com/javamonkey/ebm

beetl1

测试用例二 http://git.oschina.net/kiang/teb

beetl2

测试用例三 https://github.com/javamonkey/template-benchmark

beetl3

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

5.7. Beetl 开发团队

作者

  • 闲.大赋

助手

  • 作死模式:核心代码开发
  • 一粟蜉蝣:核心代码开发和版本发布

代码捐助者

  • 逝水fox :出色完成spring集成
  • kraken: 集合方法等扩展
  • 西安玛雅牛:复合加载器
  • 级?!: beetl扩展,crossMVC
  • orangetys: beetl插件
  • Oo不懂oO: beetl插件
  • 原上一颗草:Beetl早期使用者。
  • 龙图腾飞 ,WebErrorHandler,用来开发模式在 web上显示错误而不是控制台
  • nutz: nutz 集成和MapResourceLoader
  • 天方地圆 :提供正则方法

文档校验

  • 九月
  • Daemons
  • Darren

5.8 Beetl常用错误解决

5.8.1 模板加载错误

MVC框架如果加载不到模板,请先确认是否指定了正确的ResourceLoader。对于Spring Boot,使用的是ClassPathResourceLoaer,加载位于templates目录下的模板

对于其他WEB应用,内部使用的是FileResourceLoader,模板根目录位于web根目录。

Spring常见模板加载问题有可能如下原因

  • spring 配置使用了前缀,错误:

~xml
~

可以指定模板根目录

~xml
~

  • spring 视图名使用了相对路径,错误 ~java return "userDetail.btl"
    ~
    应该使用如下 ~java return "/user/user.btl"
    ~

  • Spring Boot 自定义模板根目录

如果模板不在resources/templates目录下,比如在resouces/pages/views下,应该用如下方式初始化

~java ClasspathResourceLoader cploder = new ClasspathResourceLoader(BeetlTemplateConfig.class.getClassLoader(),
"pages/views"); beetlGroupUtilConfiguration.setResourceLoader(cploder);

~

如果以上办法如果还不行,请尝试调试ResourceLoader的exist的方法,找到加载模板不成功原因

5.8.2 开发模式下需改模板未刷新。

这种现象主要出现在idea +maven的工程里,因为idea默认情况下不会同步模板文件到target某,因此即使你修改了模板,beetl也看不到变化。解决办法可以参考 渔泯小镇

http://bbs.ibeetl.com/bbs/bbs/topic/612-1.html

如果是其他环境出现这个问题,请确认修改的模板是否同步到目标环境里

5.8.3 错误提示里有“directive dynamic "

Beetl默认使用了了如下引擎
~properties ENGINE=org.beetl.core.engine.FastRuntimeEngine
~
这个引擎会假设同一个模板里的同一个全局变量应该类型唯一,如果你的模板是公共模板,类型不一样,可以在模板顶部使用dynamic,比如

~java <% directive dynamic xxx %>

~
如果你的模板这种情况很多,建议更换成默认引擎配置

~properties ENGINE=org.beetl.core.engine.DefaultTemplateEngine
~

5.8.4 Spring Boot 出现 ClassCastException

Spring Boot 需要配置 spring-devtools.properties,请参考Spring Boot集成



转载请注明:ITCTO技术博客 » http://www.itcto.cn/beetl/

分享到:
主题颜色面板