<%
//ajax片段开始
#ajax userTable: {
%>
id | 姓名 |
<% for(user in users){ %>
${user.id} | ${user.name} |
<% } %>
当前页面
${page!1}
next pre
<%
//ajax片段结尾
}
%>
```
\#ajax 用于告诉告诉模板引擎,此处是个局部渲染标记,标记为"userTable",对于正常渲染视图"index.html"页面,#ajax标记没什么用处,table仍能得到正常渲染。如果渲染的视图是index.html#userTable,则模板只会渲染#ajax标记得模板片段,其他部分将忽略。关于完整例子,可以参考[http://beetlajax.oschina.mopaas.com/](http://beetlajax.oschina.mopaas.com/)
后台代码如下:
```javascript
render("/index.html#userTable");
```
ajax 片段渲染也支持默认情况下不渲染,仅仅做为一个片段使用,如一个页面有许多后台交互操作,并返回相应的html片段,可以将这些html片段也放到同一个模板里,使用ajax norender,表示渲染整个模板的时候默认并不需要渲染此ajax片段
```javascript
<%
#ajax norender success: {
%>
操作成功
<%
}
%>
#ajax norender failure: {
%>
操作失败
<%
}
%>
```
这样,此页面默认情况下并没有输出success,和 failure片段
>注意,Ajax片段本质上是从模版的ajax标记处开始渲染,因此,ajax需要的变量在模版里也必须是全局变量,如果你只是个局部变量,beetl会报出找不到变量,即使你binding了这个变量,beetl也认为这个是局部变量,如
>
>```javascript
><%
>var tableData = paras.table;
>#ajax userTable: {
>for(user in tableData);
>%>
>
><%
>//ajax片段结尾
>}
>%>
>```
>
>变量tableData是从paras里获取的,是个临时变量,因此就算你在后台binding了一个tableData,beetl 也不能识别。在渲染ajax片段的时候会报变量tableData找不到。改正的办法只能是让tableData全局变量。
> 返回Json好还是返回html片段好?这个难以定论.
>
> - 从后台性能看,将模型序列化成json性能会比渲染模板性能更好,但是,json还需要前端重新解析生成最终html dom节点,这可能会延迟最终数据的现实效果。而返回的html片段就是已经生成好的dom
> - 从网络传入来看,json无疑更好的,html片段会有额外的html标记,css属性,以及有可能的js调用。传入流量有可能增加50%到100%。但是,对于web应用类,这些额外数据,并不算多。
> - 从开发效率来讲,返回html片段的开发效率更高一些,因为渲染在后台操作,可以随心所欲的用模板语言来渲染,来取得后台数据,完成复杂渲染,而json就比较困难,可以说所有的json lib都没有完美的解决办法。
> - 从用户体验上来讲,Beetl 采用ajax标记,混合了传统的模板渲染和ajax加载。用户进入页面即能看到数据,而经典的ajax json方式还需要异步加载,显示延迟。另外如果页面同时有多个ajax加载,则会对服务器造成很大的压力。
> - 关心服务器cpu消耗? 模板方式消耗更多的cpu,json方式则少点。但是俩者差距并不大。而且更多的web网站面临的情况是有富余的服务器CPU能力
> - 关心客户端CPU消耗? 过多的js无疑是客户端运行慢的主要原因。如果采用经典的json方式,返回的json数据必然还需要经过js的计算和渲染。会影响客户机器cpu。
#### 4.13. 在页面输出错误提示信息
2.2.3版本以后,新增加org.beetl.ext.web.WebErrorHandler,可以在web开发的时候在页面输出提示信息,在产品模式下在后台输出提示信息(通过配置属性ESOURCE.autoCheck= true来认为是开发模式),仅仅需要配置如下:
```properties
ERROR_HANDLER = org.beetl.ext.web.WebErrorHandler
```
### 5. 附录
#### 5.1. 内置方法
##### 5.1.1. 常用内置方法
- **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,则抛出异常
- **trunc** 截取数字,保留指定的小数位,如trunc(12.456,2) 输出是12.45
- **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](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);
##### 5.1.2. 字符串相关方法
> strutil方法对参数均不做空指针检测,你可自定义方法来覆盖这些内置的方法
- **strutil.startWith** ${ strutil.startWith(“hello”,”he”) 输出是true
- **strutil.endWith** ${ strutil.endWith(“hello”,”o”) 输出是true
- **strutil.length** ${ strutil. length (“hello”),输出是5
- **strutil.subString** ${ strutil.subString (“hello”,1),输出是“ello”
- strutil.subStringTo ${ strutil.subStringTo (“hello”,1,2),输出是“e”
- **strutil.split** ${ strutil.split (“hello,joeli”,”,”),输出是数组,有俩个元素,第一个是hello,第二个是joelli”
- strutil.contain ${ strutil.contain (“hello,”el”),输出是true
- **strutil.toUpperCase** ${ strutil.toUpperCase (“hello”),输出是HELLO
- **strutil.toLowerCase** ${ strutil.toLowerCase (“Hello”),输出是hello
- **strutil.replace** ${ strutil.replace (“Hello”,”lo”,”loooo”),输出是helloooo
- strutil.format ${ strutil.format (“hello,{0}, my age is {1}”,”joeli”,15),输出是hello,joelli, my age is 15. 具体请参考[http://docs.oracle.com/javase/6/docs/api/java/text/MessageFormat.html](http://docs.oracle.com/javase/6/docs/api/java/text/MessageFormat.html)
- **strutil.trim** 去掉字符串的尾部空格
- **strutil.formatDate** var a = strutil.formatDate(user.bir,’yyyy-MM-dd’);
- **strutil.index** var index = strutil.index("abc","a");返回 索引0
- **strutil.lastIndex** var index = strutil.lastIndex("aba","a");返回索引2
##### 5.1.3. 数组相关方法
- **array.range** 返回数组或者Collection一部分,接受三个参数,第一个是数组或者Collection子类,第二,三个参数分别是起始位置
- **array.remove** 删除某个数组或者Collection的一个元素,并返回该数组或者Collection.第一个是数组或者Collection子类,第二个参数是元素
- **array.add** 向数组或者Collection添加一个元素,并返回该数组或者Collection。第一个是数组或者Collection子类,第二个参数是元素
- array.contain 判断数组或者元素是否包含元素,如果包含,返回true。否则false。第一个是数组或者Collection子类,第二个参数是元素
- **array.toArray** 转化成数组,如array.toArray(1,2,"a");
- **array.collection2Array** 将java集合转化为数组 array.collection2Array([1,2,''])
##### 5.1.4. 正则表达式相关方法
- **reg.match(str,regex)** str为需要处理的字符串,regex是表达式
- **reg.replace(str,regex,replace)**,str为需要处理的字符串,regex是表达式,替换的字符串替换字符串
- **reg.find(str,regex)** 返回找到的符合表达式的第一个字符串,否则返回空字符串
- **reg.findList(str,regex)** 找到所有符合表达式的字符串,否则返回空列表
- **reg.split(str,regex)**,对字符串进行切分,返回列表
- **reg.split(str,regex,limit)** 同上,limit是最多返回个数
##### 5.1.5. Spring 相关函数
Spring函数并没有内置,需要注册,如下
```xml
```
spel(spelString, rootObject) SpEL方法传入一个Spring SpEL表达式以获取表达式结果,方法建议以函数的方式定义在BeetlGroupUtilConfiguration的functions中
spelString: SpEL表达式字符串,必传(否则返回null) rootObject: 作为spel的根对象(对应#root),可以是一个Map或Bean对象,默认取空Map。由于Beetl运行上下文无法直接获取模版局部变量的变量名,建议局部变量采用自定义Map的方式传入
- 列表筛选(以自定义Map为根对象传入局部变量)
```javascript
<% var intArray = [12, 1, 2, 3]; %>
${spel('#root.intArray.?[#this>10]', {intArray: intArray})}
```
- 以Bean对象为根对象
```javascript
<% var now = date(); %>
${spel('#root.year + 1900', now)}
```
- 直接new对象
```javascript
${spel('(new java.util.Date()).year + 1900')}
```
- 直接引用Spring Bean
```javascript
${spel('@testBean')}
```
- 默认变量
- \#root 表示SpEL的根对象, 由spel函数第二参数传入,默认是一个空map
- \#context 表示Beetl执行上下文
- \#global 表示Beetl的共享变量Map,由于Beetl上下文无法获取临时变量名,临时变量建议使用根对象的方式传入
- \#ctxPath 表示Servlet Context Path(由Beetl WebRender提供)
- \#servlet 可以从中获取到Servlet request,response,session原生实例(由Beetl WebRender提供)
- \#parameter 表示请求参数Map(由Beetl WebRender提供)
- \#request 表示请求对象(由Beetl WebRender提供)
- \#session 表示会话域属性Map(由Beetl WebRender提供)
sputil 提供了spring内置的一些功能,如
```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
```
Spring Security Expression相关语法,请阅读: [http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#el-access](http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#el-access)
##### 5.1.7. shiro
参考文档 [https://my.oschina.net/xiandafu/blog/143109](https://my.oschina.net/xiandafu/blog/143109)
#### 5.2. 内置格式化方法
- dateFormat 日期格式化函数,如 $*yyyy-Mm-dd*,等于符号后的参数也可以没有,则使用本地默认来做格式化如 ${date,dateFormat}
- numberFormat ${0.345,numberFormat="#.%"} 输出是 34.5%,具体请参考文档 [http://docs.oracle.com/javase/6/docs/api/java/text/DecimalFormat.html](http://docs.oracle.com/javase/6/docs/api/java/text/DecimalFormat.html)
#### 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,避免频繁创建和销毁数组
- 编译引擎将模板编译成类,会产生大量的类,虚拟机很难对这些做优化。而解释引擎只有几十个固定的类,虚拟机容易优化
> #### 相关文章
>
> - 为什么JSP会比Beetl慢 [http://my.oschina.net/xiandafu/blog/475740](http://my.oschina.net/xiandafu/blog/475740)
> - Beetl 性能揭秘 2 :语言如何存取变量 [http://my.oschina.net/xiandafu/blog/293167](http://my.oschina.net/xiandafu/blog/293167)
> - Beetl 性能揭秘 1 :如何输出一个整型变量 [http://my.oschina.net/xiandafu/blog/284823](http://my.oschina.net/xiandafu/blog/284823)
#### 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。
11. ctrl-/ 单行注释,或者取消注释
12. 通常eclipse具有的快捷操作方式,beetl仍然予以保留不变
13. 具备一定的错误提示,目前只提示第一个发现的错误。
14. 双击{ } 可以选中之间的内容
#### 5.6. 性能测试对比
测试用例一 [https://github.com/javamonkey/ebm](https://github.com/javamonkey/ebm)
![beetl1](static/beetl1.jpg)
测试用例二 [http://git.oschina.net/kiang/teb](http://git.oschina.net/kiang/teb)
![beetl2](static/beetl2.png)
测试用例三 [https://github.com/javamonkey/template-benchmark](https://github.com/javamonkey/template-benchmark)
![beetl3](static/beetl3.png)
| 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。