附录

附录

内置方法

常用内置方法

  • date 返回一个java.util.Date类型的变量,如 date() 返回一个当前时间(对应java的java.util.Date); ${date( "2011-1-1" , "yyyy-MM-dd" )} 返回指定日期
  • print 打印一个对象 print(user.name);
  • println 打印一个对象以及回车换行符号,回车换号符号使用的是模板本身的,而不是本地系统的.如果仅仅打印一个换行符,则直接调用println() 即可
  • printFile 直接答应文件,文件路径以模板根目录为相对目录,printFile(‘‘/common/header.html’’);
  • 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,'yyyy')返回"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();

字符串相关方法

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,joeli, my age is 15. 具体请参考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

数组相关方法

  • 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,''])

正则表达式相关方法

  • 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是最多返回个数

Spring 相关函数

Spring函数并没有内置,需要注册,如下

  1. <bean name="beetlGroupUtilConfiguration" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init">
  2. <property name="functions">
  3. <map>
  4. <!-- 定义SpEL方法 -->
  5. <entry key="spel">
  6. <bean class="org.beetl.ext.spring.SpELFunction"/>
  7. </entry>
  8. </map>
  9. </property>
  10. <property name="functionPackages">
  11. <map>
  12. <entry key="sputil">
  13. <bean class="org.beetl.ext.spring.UtilsFunctionPackage"/>
  14. </entry>
  15. </map>
  16. </property>
  17. </bean>

spel(spelString, rootObject) SpEL方法传入一个Spring SpEL表达式以获取表达式结果,方法建议以函数的方式定义在BeetlGroupUtilConfiguration的functions中

spelString: SpEL表达式字符串,必传(否则返回null) rootObject: 作为spel的根对象(对应#root),可以是一个Map或Bean对象,默认取空Map。由于Beetl运行上下文无法直接获取模版局部变量的变量名,建议局部变量采用自定义Map的方式传入

  • 列表筛选(以自定义Map为根对象传入局部变量)
  1. <% var intArray = [12, 1, 2, 3]; %>
  2. ${spel('#root.intArray.?[#this>10]', {intArray: intArray})}
  • 以Bean对象为根对象
  1. <% var now = date(); %>
  2. ${spel('#root.year + 1900', now)}
  • 直接new对象
  1. ${spel('(new java.util.Date()).year + 1900')}
  • 直接引用Spring Bean
  1. ${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内置的一些功能,如

  1. // 测试source中是否包含了candidates的某个成员(相当于交集非空)
  2. sputil.containsAny(Collection<?> source, Collection<?> candidates)
  3. // 返回在source集合总第一个也属于candidates集的元素
  4. sputil.findFirstMatch(Collection<?> source, Collection<?> candidates)
  5. // 测试指定文本是否匹配指定的Ant表达式(\*表达式), 多个表达式只要一个匹配即可
  6. sputil.antMatch(String input, String... patterns)
  7. // 返回指定路径表示的文件的扩展名(不带点.)
  8. sputil.fileExtension(String path)
  9. // 忽略大小写的endsWith
  10. sputil.endsWithIgnoreCase(String input, String suffix)
  11. // 忽略大小写的startsWith
  12. sputil.startsWithIgnoreCase(String input, String prefix)
  13. // 测试输入值是否为空白, null视为空白, 无视字符串中的空白字符
  14. sputil.isBlank(String input)
  15. // 首字母大写转换
  16. sputil.capitalize(String input)
  17. // 首字母小写转换
  18. sputil.uncapitalize(String input)
  19. // 在集合或数组元素之间拼接指定分隔符返回字符串
  20. // null表示空集, 其他类型表示单元素集合
  21. sputil.join(Object collection, String delim)
  22. // 同上, 只是会在最后结果前后加上前缀和后缀
  23. // 注意这个函数名叫做joinEx
  24. sputil.joinEx(Object collection, String delim, String prefix, String suffix)
  25. // 对文本进行html转义
  26. sputil.html(String input)
  27. // 对文本进行javascript转义
  28. sputil.javaScript(String input)

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

    示例:

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

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

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

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

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

  1. <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

shiro

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

内置格式化方法

内置标签函数

  • include include一个模板,如 :
  1. <% include("/header.html"){} %>

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

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

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

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

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

    content.html内容如下:

  1. <%
  2. //content.html内容如下:
  3. layout("/inc/layout.html"){ %>
  4. this is 正文
  5. ..........
  6. <% } %>

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:

  1. 如果想往layout页面传入参数,则传入一个json变量,如下往layout.html页面传入一个用户登录时间
  2. ```javascript
  3. <% layout("/inc/header.html",{'date':user.loginDate,'title':"内容页面"}){ %>
  4. this is 正文
  5. ..........
  6. <% } %>

如果layoutContent 命名有冲突,可以在layout第三个参数指定,如

javascript <% layout("/inc/header.html",{'date':user.loginDate,'title':"内容页面"},"myLayoutContent"){ %> this is 正文 ………. <% } %> ​ ```

  • cache 能Cache标签的内容,并指定多长时间刷新,如
  1. <% :cache('key2',10,false){ %>
  2. 内容体
  3. <% } %>

需要指定三个参数

  • 第一个是cache的Key值

  • 第二个是缓存存在的时间,秒为单位

  • 第三个表示是否强制刷新,false表示不,true表示强制刷新

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

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

  1. public void clearAll();
  2. public void clearAll(String key);
  3. public void clearAll(String... keys);
  • includeJSP,可以在模板里包括一个jsp文件,如:
  1. <%
  2. includeJSP("/xxxx.jsp",{"key":"value"}){}
  3. %>

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

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

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

内置html标签

Beetl可以很方便的定义类似html的标签,目前内置了如下标签

  1. TAG.html.include= org.beetl.ext.tag.html.IncludeResourceHtmlTag
  2. TAG.html.layout= org.beetl.ext.tag.html.LayoutResourceHtmlTag
  3. TAG.html.set= org.beetl.ext.tag.html.SetHtmlTag
  4. TAG.html.if= org.beetl.ext.tag.html.IfHtmlTag
  5. TAG.html.foreach= org.beetl.ext.tag.html.ForeachHtmlTag

如下例子

  1. <#html:include file="" arg1="" arg2="" />

include包含file属性指名一个模板路径,后面若干属性可选,会在子模板里引用

  1. <#html:layout parent="" args1 arg2="">
  2. .......
  3. </#html:layout>

同layout标签函数,包含必须的parent,指明布局模板路径

html:set, 使用export 设置一个变量名,在此后可以使用此变量名引用这个变量(export和var的区别,前者申明的变量可以在模板后面是使用,而var只能在标签体使用)

  1. <#html:set value="${usere.age+1}" export="age"/>

html:if,包含test用于测试条件是否为真,如果为真,执行标签体

  1. <#html:if test="${condtion}">
  2. </#html:if>

html:for,通过items申明要循环的变量,通过var申明循环元素的名称,可以申明一个状态,这个与for循环一样,是一个ILoopStatus对象

  1. <#html:foreach items="${[1,5,3]}" var="item">
  2. ${item}
  3. </#html:foreach>
  4. <#html:foreach items="${[1,5,3]}" var="item,status">
  5. ${status.index}--${item}
  6. </#html:foreach>

性能优化

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,避免频繁创建和销毁数组
  • 编译引擎将模板编译成类,会产生大量的类,虚拟机很难对这些做优化。而解释引擎只有几十个固定的类,虚拟机容易优化

相关文章

Eclipse 插件

  • 启动Eclipse

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

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

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

http://ibeetl.com/eclipse/

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

使用说明:

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

性能测试对比

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

beetl1

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

beetl2

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

beetl3

BenchmarkversionThreadsSamplesScoreScore Error (99.9%)Unit
Beetl2.715042125.1129143512.147131ops/s
Freemarker2.315013099.139808339.612022ops/s
Handlebars4.015015808.044125235.109622ops/s
Mustache0.915017961.391809158.524109ops/s
Rocker0.115033631.370722417.915637ops/s
Thymeleaf3.01504625.98127667.313609ops/s

注意

Score得分越高表示模板引擎每秒处理量越大性能越好

这个性能测试基本上结合了国内外的模板引擎,随着JDK版本的升级,JDK8提高了反射能力,减少了和Freemarker等模板引擎的性能差距,但Beetl依旧以3倍以上的性能优势秒杀Freemarker。

Beetl常用错误解决

模板加载错误

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

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

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

  • spring 配置使用了前缀,错误:
  1. <property name="prefix" value="/WEB-INF/view/"></property>

可以指定模板根目录

  1. <bean name="beetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init">
  2. <property name="root" value="/WEB-INF/beetl.properties"/>
  • spring 视图名使用了相对路径,错误
  1. return "userDetail.btl"

应该使用如下

  1. return "/user/user.btl"
  • Spring Boot 自定义模板根目录

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

  1. ClasspathResourceLoader cploder = new ClasspathResourceLoader(BeetlTemplateConfig.class.getClassLoader(),
  2. "pages/views");
  3. beetlGroupUtilConfiguration.setResourceLoader(cploder);

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

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

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

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

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

错误提示里有“directive dynamic "(老版本里有)

Beetl使用FastRuntimeEngine有可能导致这个问题

  1. ENGINE=org.beetl.core.engine.FastRuntimeEngine

这个引擎会假设同一个模板里的同一个全局变量应该类型唯一,如果你的模板是公共模板,类型不一样,可以在模板顶部使用dynamic,比如

  1. <% directive dynamic xxx %>

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

  1. ENGINE=org.beetl.core.engine.DefaultTemplateEngine

还有种情况是在Spring Boot 下出现,参考下一节

Spring Boot 出现 ClassCastException(老版本里有)

请使用最新的Beetl版本,使用Starter或者参考Spring Boot集成一章集成Spring Boot。这是Spring Boot dev模式引起的问题