Beetl2.8 中文文檔

1. 什麼是Beetl

Beetl目前版本是2.8.5,相對於其餘java模板引擎,具備功能齊全,語法直觀,性能超高,以及編寫的模板容易維護等特色。使得開發和維護模板有很好的體驗。是新一代的模板引擎。總得來講,它的特性以下:javascript

  • 功能完備:做爲主流模板引擎,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也支持自定義佔位符和控制語句起始符號也支持使用者徹底能夠打造適合本身的工具包。
  • 能夠擴展爲腳本引擎,規則引擎,能定製引擎從而實現高級功能。

關於性能

在使用FastRuntimeEngine狀況下,經過與主流模板引擎Freemarker,Vecloity以及JSP對比,Beetl6倍於Freemarker,2倍於JSP。這是由於宏觀上,經過了優化的渲染引擎,IO的二進制輸出,字節碼屬性訪問加強,微觀上,經過一維數組保存上下文Context,靜態文本合併處理,經過重複使用字節數組來防止java頻繁的建立和銷燬數組,還使用模板緩存,運行時優化等方法。詳情參考附錄css

獨特功能

Beetl有些功能是發展了10多年的模板引擎所不具有的,這些功能很是利於模板的開發和維護,以下html

  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(滿) 636321496java

郵件:xiandafu@126.comgit

Beetl社區:bbs.ibeetl.com程序員

源碼主頁:https://github.com/javamonkey/beetl2.0github

在線體驗和代碼分享 http://ibeetl.com/beetlonline/web

2. 基本用法

2.1. 安裝

若是使用maven,請使用以下座標ajax

<dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl</artifactId> <version>2.8.5</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提供了多種得到渲染輸出的方法,以下

  • template.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行模板字符集的配置就能夠了,其餘配置會在後面章節陸續提到,同時,對於Spring等框架,有些配置將會被這些框架的配置覆蓋,須要參考後面章節)下,其內容片段以下:

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

這配置文件總體說明了Beetl提供的功能

第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的配置屬性,若是有重複,用後者代替前者的配置

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

2.4.0 新功能:beetl 支持經過模板本生來完成函數,即模板函數,或者經過模板來實現HTML標籤(而不用寫java代碼),能夠beetl.properties爲這種應用設置的不一樣的語句定界符來跟常規模板作區分,以下

FUNCTION_TAG_LIMITER=<% ; %> 

分號分割開,若是配置文件沒有FUNCTION_TAG_LIMITER=,則模板函數,html標籤使用同DELIMITER_STATEMENT_START,DELIMITER_STATEMENT_END

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 帶來的這種非人性語法

定界符和佔位符 一般還有別的選擇,以下定界符

  • @ 和回車換行 (此時,模板配置DELIMITER_STATEMENT_END= 或者 DELIMITER_STATEMENT_END=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; //定義一個變量. /* 如下內容都將被註釋 %> <% */ %> 

第2行是一個多行註釋

第3行是一個單行註釋

第5行到第8行採用的是多行註釋,所以裏面有內容也是註釋,模板將不予處理

2.7. 臨時變量定義

在模板中定義的變量成爲臨時變量,這相似js中採用var 定義的變量,以下例子

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

2.8. 全局變量定義

全局變量是經過template.binding傳入的變量,這些變量能在模板的任何一個地方,包括子模板都能訪問到。如java代碼裏

template.binding("list",service.getUserList()); //在模板裏 <% for(user in list){ %> hello,${user.name}; <% } %> 

自從2.8.0版本後,有一個特殊的變量成爲root變量,當模板找不到變量的時候,會尋找root變量的屬性來做爲變量的值,這個root變量必須綁定爲"_root"

template.binding("_root",new User()); //在模板裏 ${name} ${wife.name} 

這裏name 和 wife都是User對象的屬性

2.9. 共享變量

共享變量指在全部模板中均可以引用的變量,可經過groupTemplate.setSharedVars(Map<String, Object> 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} 

2.10. 模板變量

模板變量是一種特殊的變量,便可以將模板中任何一段的輸出賦值到該變量,並容許稍後在其餘地方使用,以下代碼

<% var content = { var c = "1234"; print(c); %> 模板其餘內容: <% }; %> 

第2行定義了一個模板變量content = { …} ; 此變量跟臨時變量同樣,能夠在其餘地方使用,最多見的用法是用於複雜的佈局。請參考高級用法佈局

2.11. 引用屬性

屬性引用是模板中的重要一部分,beetl支持屬性同javascript的支持方式同樣,以下

  1. Beetl支持經過」.」號來訪問對象的的屬性,若是javascript同樣。若是User對象有個getName()方法,那麼在模板中,能夠經過${xxx.name}來訪問
  2. 若是模板變量是數組或者List類,這能夠經過[] 來訪問,如${userList[0]}
  3. 若是模板變量是Map類,這能夠經過[]來訪問,如${map[「name」]},若是key值是字符串類型,也可使用${map.name}.但不建議這麼使用,由於會讓模板閱讀者誤覺得是一個Pojo對象
  4. Beetl也支持Generic Get方式,即若是對象有一個public Object get(String key)方法,能夠經過」.」號或者[]來訪問,譬如 ${activityRecord.name}或者${activityRecord[「name」] }都將調用activityRecord的 get(String key)方法。若是對象既有具體屬性,又有Generic get(這種模型設計方式是不值得鼓勵),則以具體屬性優先級高.
  5. Beetl也能夠經過[]來引用屬性,如${user[「name」]} 至關於${user.name}.這跟javascript保持一致。但建議不這麼作,由於容易讓閱讀模板的人誤認爲這是一個Map類型
  6. Beetl 還能夠定義額外的對象屬性,而無需更改java對象,這叫着虛擬屬性,如,對於全部集合,數組,都有共同的虛擬屬性size.虛擬屬性是「.~」+虛擬屬性名
template.binding("list",service.getUserList()); template.binding("pageMap",service.getPage()); //在模板裏 總共 ${list.~size} <% for(user in list){ %> hello,${user.name}; <% } %> 當前頁${pageMap['page']},總共${pageMap["total"]} 

2.12. 屬性賦值

Beetl2.7.0 開始支持對象賦值,如:

<% var user = .... user.name="joelli"; user.friends[0] = getNewUser(); user.map["name"] = "joelli"; %> 

2.13. 算數表達式

Beetl支持相似javascript的算術表達式和條件表達式,如+ - * / % 以及(),以及自增++,自減--

<%
var a = 1; var b = "hi"; var c = a++; var d = a+100.232; var e = (d+12)*a; var f = 122228833330322.1112h %> 

Beetl裏定義的臨時變量類型默認對應的java類型是Int型或者double類型,對於模板經常使用狀況,已經夠了.若是須要定義長精度類型(對應java的BigDecimal),則須要在數字末尾加上h以表示這是長精度BigDecimal,其後的計算和輸出以及邏輯表達式都將按照長精度類型來考慮。

2.14. 邏輯表達式

Beetl支持相似Javascript,java的條件表達式 如>,\<,==,!=,>= , \<= 以及 !, 還有&&和 || ,還有三元表達式等,以下例子

<%
var a = 1; var b="good"; var c = null; if(a!=1&&b=="good"&&c==null){ ...... } %> 

三元表達式若是隻考慮true條件對應的值的話,能夠作簡化,以下倆行效果是同樣的。

<%
 var  a = 1 ;
%> ${a==1?"ok":''} ${a==1?"ok"} 

2.15. 循環語句

Beetl支持豐富的循環方式,如for-in,for(exp;exp;exp),以及while循環,以及循環控制語句break;continue; 另外,若是沒有進入for循環體,還能夠執行elsefor指定的語句。

2.15.1. for-in

for-in循環支持遍歷集合對象,對於List和數組來講以及Iterator,對象就是集合對象,對於Map來講,對象就是Map.entry,以下倆個例子

<%
for(user in userList){ print(userLP.index); print(user.name); } %> 

第三行代碼userLP是Beetl隱含定義的變量,能在循環體內使用。其命名規範是item名稱後加上LP,他提供了當前循環的信息,如

  • userLP.index 當前的索引,從1開始
  • userLP.size 集合的長度
  • userLP.first 是不是第一個
  • userLP.last 是不是最後一個
  • userLP.even 索引是不是偶數
  • userLP.odd 索引是不是奇數

如何記住後綴是LP,有倆個訣竅,英語棒的是Loop的縮寫,拼音好的是老婆的拼音縮寫,這可讓程序員每次寫到這的時候都會想一想老婆(無論有沒有,哈哈)

以下是Map使用例子

<%
for(entry in map){ var key = entry.key; var value = entry.value; print(value.name); } %> 
2.15.2. for(exp;exp;exp)

對於渲染邏輯更爲常見的是經典的for循環語句,以下例子

<%
var a = [1,2,3]; for(var i=0;i<a.~size;i++){ print(a[i]); } %> 
2.15.3. while

對於渲染邏輯一樣常見的有的while循環語句,以下例子

<%
var i = 0; while(i<5){ print(i); i++; } %> 
2.15.4. elsefor

不一樣於一般程序語言,若是沒有進入循環體,則不需額外的處理,模板渲染邏輯更常見狀況是若是沒有進入循環體,還須要作點什麼,所以,對於for循環來講,還有elsefor 用來表達若是循環體沒有進入,則執行elsefor 後的語句

<%
var list = []; for(item in list){ }elsefor{ print("未有記錄"); } %> 

2.16. 條件語句

2.16.1. if else

同js同樣,支持if else,以下例子

<%
var a =true; var b = 1; if(a&&b==1){ }else if(a){ }else{ } %> 
2.16.2. switch-case

同js同樣,支持switch-case,以下例子

<%
var b = 1; switch(b){ case 0: print("it's 0"); break; case 1: print("it's 1"); break; default: print("error"); } %> 

switch變量能夠支持任何類型,而不像js那樣只能是整形

2.16.3. select-case

select-case 是switch case的加強版。他容許case 裏有邏輯表達式,同時,也不須要每一個case都break一下,默認遇到合乎條件的case執行後就退出。

<%
var b = 1; select(b){ case 0,1: print("it's small int"); case 2,3: print("it's big int"); default: print("error"); } %> 

select 後也不須要一個變量,這樣case 後的邏輯表達式將決定執行哪一個case.其格式是

<%
select { case boolExp,orBoolExp2: doSomething(); } %> 
<%
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"); } %> 

2.17. try-catch

一般模板渲染邏輯不多用到try-catch 但考慮到渲染邏輯複雜性,以及模板也有不可控的地方,因此提供try catch,在渲染失敗的時候仍然能保證輸出正常

<%
try{ callOtherSystemView() }catch(error){ print("暫時無數據"); } %> 

error表明了一個異常,你能夠經過error.message 來獲取可能的錯誤信息

也能夠省略catch部分,這樣出現異常,不作任何操做

2.18. 虛擬屬性

虛擬屬性也是對象的屬性,是虛擬的,非模型對象的真實屬性,這樣的好處是當模板須要額外的用於顯示的屬性的時候但又不想更改模型,即可以採用這種辦法 如beetl內置的虛擬屬性.~size 針對了數組以及集合類型。

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

~genderShowName 是虛擬屬性,其內部實現根據boolean變量gender來顯示性別

如何完成虛擬屬性,請參考高級用法

2.19. 函數調用

Beetl內置了少許實用函數,能夠在Beetl任何地方調用。以下例子是調用date 函數,不傳參數狀況下,返回當前日期

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

注意函數名支持namespace方式,所以代碼第3行調用的函數是strutil.length

定義beetl的方法很是容易,有三種方法

  • 實現Function類的call方法,並添加到配置文件裏,或者顯示的經過代碼註冊registerFunction(name,yourFunction)
  • 能夠直接調用registerFunctionPackage(namespace,yourJavaObject),這時候yourJavaObject裏的全部public方法都將註冊爲Beetl方法,方法名是namespace+"."+方法名
  • 能夠直接寫模板文件而且以html做爲後綴,放到root/functions目錄下,這樣此模板文件自動註冊爲一個函數,其函數名是該模板文件名。

詳情請參考高級用法

Beetl內置函數請參考附錄,如下列出了經常使用的函數

  • date 返回一個java.util.Date類型的變量,如 date() 返回一個當前時間(對應java的java.util.Date); ${date( "2011-1-1" , "yyyy-MM-dd" )} 返回指定日期,date(ms),指定一個毫秒數。至關於調用java.util.Date(ms)
  • print 打印一個對象 print(user.name);
  • println 打印一個對象以及回車換行符號,回車換號符號使用的是模板自己的,而不是本地系統的.若是僅僅打印一個換行符,則直接調用println() 便可
  • nvl 函數nvl,若是對象爲null,則返回第二個參數,不然,返回本身 nvl(user,"不存在")
  • isEmpty 判斷變量或者表達式是否爲空,變量不存在,變量爲null,變量是空字符串,變量是空集合,變量是空數組,此函數都將返回true
  • isNotEmpty 同上,判斷對象是否不爲空
  • has 變量名爲參數,判斷是否存在此"全局變量",如 has(userList),相似於1.x版本的exist("userList"),但不須要輸入引號了.注意,has和isEmpety 判斷的是從java傳到模板的全局變量,而不是臨時變量
  • hasAttriute 測試目標對象是否有此屬性,hasAttribute(user,"name")
  • 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();

2.20. 安全輸出

安全輸出是任何一個模板引擎必須重視的問題,不然,將極大困擾模板開發者。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.birthday!@System.constants.DefaultBir}, 表示若是user爲null,或者user. birthday爲null,輸出System.constants.DefaultBir

還有一種狀況不多發生,但也有可能,輸出模板變量發生的任何異常,如變量內部拋出的一個異常

這須要使用格式${!(變量)},這樣,在變量引用發生任何異常狀況下,都不做輸出,譬如

${!(user.name)},,beetl將會調用user.getName()方法,若是發生異常,beetl將會忽略此異常,繼續渲染

值得注意的是,在變量後加上!不只僅能夠應用於佔位符輸出(但主要是應用於佔位符輸出),也能夠用於表達式中,如:

<%
var k = user.name!'N/A'+user.age!;
%> <% ${k} %> 

若是user爲null,則k值將爲N/A

在有些模板裏,可能整個模板都須要安全輸出,也可能模板的部分須要安全輸出,使用者沒必要爲每個表達式使用!,可使用beetl的安全指示符號來完成安全輸出 如:

<%
DIRECTIVE SAFE_OUTPUT_OPEN;
%> ${user.wife.name} 模板其餘內容,均能安全輸出…… <% //關閉安全輸出。 DIRECTIVE SAFE_OUTPUT_CLOSE; %> 

Beetl不建議每個頁面都使用DIRECTIVE SAFE_OUTPUT_OPEN,這樣,若是真有不指望的錯誤,不容易及時發現,其次,安全輸出意味着beetl會有額外的代碼檢測值是否存在或者是否爲null,性能會略差點。因此建議及時關閉安全輸出(這不是必須的,但頁面全部地方是安全輸出,可能不容易發現錯誤)

在for-in 循環中 ,也能夠爲集合變量增長安全輸出指示符號,這樣,若是集合變量爲null,也能夠不進入循環體,如:

<%
var list = null; for(item in list!){ }elsefor{ print("no data"); } %> 
2.20.1. 變量是否存在
<%
if(has(flag)){ print("flag變量存在,能夠訪問") } %> 

若是須要判斷變量是否存在,若是存在,還有其餘判斷條件,一般都這麼寫

<%
if(has(flag)&&flag==0){ //code } %> 

若是flag存在,並且值是0,都將執行if語句

可是,有更爲簡便的方法是直接用安全輸出,如

<%
if(flag!0==0){ //code } %> 

flag!0 取值是這樣的,若是flag不存在,則爲0,若是存在,則取值flag的值,相似三元表達式 if((has(flag)?flag:0)==0)

2.20.2. 安全輸出表達式

安全輸出表達式能夠包括

  • 字符串常量,如 ${user.count!"無結果"}
  • boolean常量 ${user.count!false}
  • 數字常量,僅限於正數,由於若是是負數,則相似減號,容易誤用,所以,若是須要表示負數,請用括號,如${user.count!(-1)}
  • class直接調用,如${user.count!@User.DEFAULT_NUM}
  • 方法調用,如 ${user.count!getDefault() }
  • 屬性引用,如 ${user.count!user.maxCount }
  • 任何表達式,須要用括號

2.21. 格式化

幾乎全部的模板語言都支持格式化,Beetl也不列外,以下例子Beetl提供的內置日期格式

<% var date = date(); %> Today is ${date,dateFormat="yyyy-MM-dd"}. Today is ${date,dateFormat} salary is ${salary,numberFormat="##.##"} 

格式化函數只須要一個字符串做爲參數放在=號後面,若是沒有爲格式化函數輸入參數,則使用默認值,dateFormat格式化函數默認值是local

Beetl也容許爲指定的java class設定格式化函數,譬如已經內置了對java.util.Date,java.sql.Date 設置了了格式化函數,所以上面的例子能夠簡化爲

${date,「yyyy-MM-dd」} 

Beetl針對日期和數字類型提供的默認的格式化函數,在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 

2.22. 標籤函數

所謂標籤函數,即容許處理模板文件裏的一塊內容,功能等於同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 

第1行變量title來自於layout標籤函數的參數

第2行layoutContent 是layout標籤體{}渲染後的結果

關於layout標籤,參考高級主題佈局

Beetl內置了另一個標籤是include,容許 include 另一個模板文件

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

在標籤中,{} 內容將依據標籤的實現而執行,layout標籤將執行{}中的內容,而include標籤則忽略標籤體內容。

關於如何實現標籤函數,請參考高級主題,以下是一個簡單的的標籤函數:

public class CompressTag extends Tag{ @Override public void render(){ BodyContent content = getBodyContent(); String content = content.getBody(); String zip = compress(conent); ctx.byteWriter.write(zip); } } 

2.23. HTML標籤

Beetl 也支持HTML tag形式的標籤, 區分beetl的html tag 與 標準html tag。如設定HTML_TAG_FLAG=#,則以下html tag將被beetl解析

<#footer style=」simple」/> <#richeditor id=」rid」 path="${ctxPath}/upload" name=」rname」 maxlength=」${maxlength}」> ${html} …其餘模板內容 </#richdeitor> <#html:input id=’aaaa’ /> 

如對於標籤footer,Beetl默認會尋找WebRoot/htmltag/footer.tag(能夠經過配置文件修改路徑和後綴) ,內容以下:

<% if(style==’simple’){ %> 請聯繫我 ${session.user.name} <% }else{ %> 請聯繫我 ${session.user.name},phone:${session.user.phone} <% } %> 

以下還包含了自定義html標籤一些規則

  • 能夠在自定義標籤裏引用標籤體的內容,標籤體能夠是普通文本,beetl模板,以及嵌套的自定義標籤等。如上<#richeditor 標籤體裏,可用「tagBody」來引用
  • HTML自定義標籤 的屬性值均爲字符串 如<#input value="123" />,在input.tag文件裏 變量value的類型是字符串
  • 能夠在屬性標籤裏引用beetl變量,如<#input value="${user.age}" />,此時在input.tag裏,value的類型取決於user.age
  • 在屬性裏引用beetl變量,不支持格式化,如<#input value="${user.date,'yyyy-MM-dd'}"/>,若是須要格式化,須要在input.tag文件裏自行格式化
  • 在標籤屬性裏傳json變量須要謹慎,由於json包含了"}",容易與佔位符混合致使解析出錯,所以得使用"\"符號,如<#input value="${ {age:25} }" />
  • html tag 屬性名將做爲 其對應模板的變量名。
  • 默認機制下,全局變量都將傳給html tag對應的模板文件,這個跟include同樣。固然,這機制也能夠改變,對於標籤來講,一般是做爲一個組件存在,也不必定須要徹底傳送全部全局變量,而只傳送(request,session,這樣變量),所以須要從新繼承org.beetl.ext.tag.HTMLTagSupportWrapper.並重載callHtmlTag方法。並註冊爲htmltag標籤。具體請參考https://github.com/javamonkey/beetl2.0/blob/master/beetl-core/src/test/java/org/beetl/core/tag/HtmlTagTest.java

若是採用模板來寫html標籤功能不夠強大,beetl支持寫標籤函數(參考上一節)來實現html標籤,標籤函數args[0]表示標籤名,這一般沒有什麼用處,args[1] 則是標籤的屬性,參數是個map,key是html tag的屬性,value是其屬性值,以下用java完成的html 標籤用於輸出屬性值

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

若是註冊gt.registerTag("simpleTag", SimpleHtmlTag.class); 則以下模板輸出了attr屬性值abc

<#simpleTag attr="abc"></#simpleTag> 

HTML_TAG_FLAG默認爲#用來區別是不是beetl的html tag,你也能夠設置成其餘符號,好比 "my:",這樣,\my:table\\</my:table> 實際上是一個指向table.tag的標籤實現

2.24. 綁定變量的HTML標籤

對於html標籤(參考上一節),Beetl還 支持將標籤實現類(java代碼)裏的對象做爲臨時變量,被標籤體引用。此時須要實現GeneralVarTagBinding (此類是Tag的子類) 該類提供另外3個方法 - void binds(Object… array) 子類在render方法裏調用此類以實現變量綁定,綁定順序同在模板中聲明的順序 - void bind(String name, Object value) 子類在render方法裏調用此類以實現變量綁定,name是模板中聲明的變量名,用此方法綁定不如binds更靈活,再也不推薦 - Object getAttributeValue 得到標籤的屬性 - Map getAttributes 得到標籤的全部屬性

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

如上例子,render方法將循環渲染標籤體limit次,且每次都將value賦值爲i。咱們再看看模板如何寫的

<#tag limit="3";value> ${value} </#tag> 

相似於常規html標籤,須要在標籤的最後的屬性定義後面加上分號 ";" 此分號表示這個是一個須要在標籤運行時須要綁定變量的標籤。後跟上要綁定的變量列表,如上例只綁定了一個value變量,若是須要綁定多個變量,則用逗號分開,如var1,var2 上。若是後面沒有變量列表,只有分號,則默認綁定到標籤名同名的變量上. 若是標籤有namesapce,則默認綁定訂的變量名不包含namespace

注意,因爲標籤使用由於太長可能換行或者是文本格式化致使換行,目前beetl只容許在屬性之間換行,不然,將報標籤解析錯誤。

默認狀況下,若是標籤屬性出現了var(能夠經過配置文件改爲其餘屬性名),也認爲是綁定變量的標籤,如上面的例子也能夠這麼寫

<#tag limit="3" var="value"> ${value} </#tag> 

var屬性的值能夠是個以逗號分開的變量名列表,如var="total,customer,index"

2.25. 直接調用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); %> 

能夠調用instance的public方法和屬性,也能夠調用靜態類的屬性和方法 ,須要加一個 @指示此調用是直接調用class,其後的表達式是java風格的。

  • GroupTemplate能夠配置爲不容許直接調用Class,具體請參考配置文件.
  • 也能夠經過安全管理器配置到底哪些類Beetl不容許調用,具體請參考高級用法。默認狀況,java.lang.Runtime,和 java.lang.Process不容許在模板裏調用。你本身的安全管理器也能夠配置爲不能直接訪問DAO類(避免了之前jsp能夠訪問任意代碼帶來的危害)
  • 請按照java規範寫類名和方法名,屬性名。這樣便於beetl識別到底調用的是哪一個類,哪一個方法。不然會拋出錯誤
  • 能夠省略包名,只用類名。beetl將搜索包路徑找到合適的類(須要設置配置「IMPORT_PACKAGE=包名.;包名.」,包名後須要跟一個「.」, 或者調用Configuration.addPkg)方法具體請參考附件配置文件說明
  • 內部類(包括枚舉)訪問同java同樣,如User類有個內部枚舉類Gender,訪問是User$Gender
  • 表達式是java風格,但參數仍然是beetl表達式,好比 @user.sayHello(user.name).這裏user.sayHello是java調用,user.name 仍然是beetl表達式

2.26. 嚴格MVC控制

若是在配置文件中設置了嚴格MVC,則如下語法將不在模板文件裏容許,不然將報出STRICK_MVC 錯誤

  • 定義變量,爲變量賦值,如var a = 12是非法的
  • 算術表達式 如${user.age+12}是非法的
  • 除了只容許布爾之外,不容許邏輯表達式和方法調用 如if(user.gender==1)是非法的
  • 方法調用,如${subString(string,1)}是非法的
  • Class方法和屬性調用,如${@user.getName()}是非法的
  • 嚴格的MVC,很是有助於邏輯與視圖的分離,特別當邏輯與視圖是由倆個團隊來完成的。若是你嗜好嚴格MVC,能夠調用groupTemplate.enableStrict()

經過重載AntlrProgramBuilder,能夠按照本身的方法控制到底哪些語法是不容許在模板引擎中出現的,但這已經超出了Beetl模板的基礎使用

2.27. 指令

指令格式爲: DIRECTIVE 指令名 指令參數(可選) Beetl目前支持安全輸出指令,分別是

  • DIRECTIVE SAFE_OUTPUT_OPEN ; 打開安全輸出功能,此指令後的全部表達式都具備安全輸出功能,
  • DIRECTIVE SAFE_OUTPUT_CLOSE ; 關閉安全輸出功能。詳情參考安全輸出
  • DIRECTIVE DYNAMIC varName1,varName2 …指示後面的變量是動態類型,Beetl應該考慮爲Object. 也能夠省略後面的變量名,則表示模板裏全部變量都是Object
<% DIRECTIVE DYNAMIC idList;
for(value in idList) ..... 

DYNAMIC 一般用在組件模板裏,由於組件模板能夠接收任何類型的對象。如列表控件,能夠接收任何含有id和 value屬性的對象。

  1. 注意 DYNAMIC 後的變量名也容許用引號,這主要是兼容Beetl1.x版本
  2. Beetl1.x 指令都是大寫,當前版本也容許小寫,如 directive dynamic idList

2.28. 類型聲明

Beetl 本質上仍是強類型的模板引擎,即模板每一個變量類型是特定的,在模板運行過程當中,beetl 會根據全局變量自動推測出模板中各類變量和表達式類型。 也能夠經過類型申明來講明beetl全局變量的類型,以下格式

<%
/** *@type (List<User> idList,User user) */ for(value in idList) ..... 

類型申明必須放到多行註釋裏,格式是@type( … ),裏面的申明相似java方法的參數申明。正如你看到的類型申明是在註釋裏,也就代表了這在Beetl模板引擎中不是必須的,或者你只須要申明一部分便可,之因此提供可選的類型說明,是由於

  • 提升一點性能
  • 最重要的是,提升了模板的可維護性。可讓模板維護者知道變量類型,也可讓將來的ide插件根據類型聲明來提供屬性提示,重構等高級功能

須要注意的是,若是在類型聲明裏提供的是類名,而不是類全路徑,這樣必須在配置文件裏申明類的搜索路徑((須要設置配置IMPORT_PACKAGE=包名.;包名.,或者調用Configuration.addPkg)),默認的搜索路徑有java.util. 和 java.lang.

類型聲明本用於eclipse插件用來提示,但eclipse插件暫時沒有時間去作,因此類型申明如今不推薦使用

2.29. 錯誤處理

Beetl能較爲詳細的顯示錯誤緣由,包括錯誤行數,錯誤符號,錯誤內容附近的模板內容,以及錯誤緣由,若是有異常,還包括異常和異常信息。 默認狀況下,僅僅在控制檯顯示,以下代碼:

<% var a = 1; var b = a/0; %> 

運行此模板後,錯誤提示以下

>>DIV_ZERO_ERROR:0 位於3行 資源:/org/beetl/sample/s0125/error1.txt 1|<% 2|var a = 1; 3|var b = a/0; 4| %> 
<% var a = 1; var b = a var c = a+2; %> 

運行此模板後

>>缺乏符號(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| %> 
  1. 默認的錯誤處理器僅僅像後臺打印錯誤,並無拋出異常,若是須要在render錯誤時候拋出異常到控制層,則可使用org.beetl.core.ReThrowConsoleErrorHandler。不只打印異常,還拋出BeetlException
  2. 能夠自定義異常處理器,好比把錯誤輸出到 做爲渲染結果一部分輸出,或者輸出更美觀的html內容等,具體參考高級用法
  3. 能夠在配置文件不設置異常,這樣Beetl引擎將不處理異常,用戶能夠在外部來處理(能夠在外部調用ErrorHandler子類來顯示異常)

2.30. 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} 

BeetlKit 不要用於線上系統。僅僅做爲體驗Beetl功能而提供的,若是須要在線上使用這些功能,請參考該類源碼自行擴展

2.31. 瑣碎功能

  • 對齊:我發現別的模板語言要是作到對齊,很是困難,使用Beetl你徹底不用擔憂,好比velocty,stringtemlate,freemarker例子都出現了不對齊的狀況,影響了美觀,Beetl徹底無需擔憂輸出對齊
  • Escape:可使用\ 作escape 符號,如\$monkey\$ 將做爲一個普通的文本,輸出爲$monkey$.再如爲了在後加上美圓符號(佔位符剛好又是美圓符號)能夠用這倆種方式hello,it’s $money$\$, 或者Hello,it’s $money+"\$"$ 。若是要輸出\符號本生,則須要用倆個\,這點與javascript,java 語義一致.

3. 高級功能

3.1. 配置GroupTemplate

Beetl建議經過配置文件配置GroupTemplate,主要考慮到IDE插件將來可能會支持Beetl模板,模板的屬性,和函數等若是能經過配置文件獲取,將有助於IDE插件識別。 配置GroupTemplate有倆種方法

  • 配置文件: 默認配置在/org/beetl/core/beetl-default.properties 裏,Beetl首先加載此配置文件,而後再加載classpath裏的beetl.properties,並用後者覆蓋前者。配置文件經過Configuration類加載,所以加載完成後,也能夠經過此類API來修改配置信息
  • 經過調用GroupTemplate提供的方法來註冊函數,格式化函數,標籤函數等

配置文件分爲三部分,第一部分是基本配置,在第一節講到過。第二部分是資源類配置,能夠在指定資源加載類,以及資源加載器的屬性(這個配置在spring框架裏,經過spring或者springboot的配置機制實現覆蓋,並未起做用),以下

RESOURCE_LOADER=org.beetl.core.resource.ClasspathResourceLoader
#資源配置,resource後的屬性只限於特定ResourceLoader #classpath 根路徑 RESOURCE.root= / #是否檢測文件變化 RESOURCE.autouCheck= true 

第1行指定了模板加載器類,在beetl與其餘框架集成的時候,模板加載器不必定根據這個配置,好比spring,它的RESOURCE_LOADER以spring的配置爲準

第4行指定了模板根目錄的路徑,此處/ 表示位於classpath 根路徑下,同loader同樣,依賴使用的框架

第6行是否自動檢測模板變化,默認爲true,開發環境下自動檢測模板是否更改。關於如何自定義ResouceLoader,請參考下一章

配置文件第三部分是擴展部分,如方法,格式化函數等

##### 擴展 ############## ## 內置的方法 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 

FN前綴表示Function,FNP前綴表示FunctionPackage,FT表示format函數,FTC表示類的默認Format函數,TAG表示標籤類。Beetl強烈建議經過配置文件加載擴展。以便隨後IDE插件能識別這些註冊函數

3.2. 自定義方法

3.2.1. 實現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 模板當前是否處於安全輸出模式
  • 其餘屬性建議不熟悉的開發人員不要亂動
  1. call方法要求返回一個Object,若是無返回,返回null便可
  2. 爲了便於類型判斷,call方法最好返回一個具體的類,如date函數返回的就是java.util.Date
  3. call方法裏的任何異常應該拋出成Runtime異常
3.2.2. 使用普通的java類

儘管實現Function對於模板引擎來講,是效率最高的方式,但考慮到不少系統只有util類,這些類裏的方法仍然能夠註冊爲模板函數。其規則很簡單,就是該類的全部public方法。若是還須要Context 變量,則須要在方法最後一個參數加上Context便可,如

public class util{ public String print(Object a, Context ctx){ //balabala... } } 

注意

  1. 從beetl效率角度來說,採用普通類效率不如實現Function調用
  2. 採用的普通java類儘可能少同名方法。這樣效率更低。beetl調用到第一個適合的同名方法。而不像java那樣找到最匹配的
  3. 方法名支持可變數組做爲參數
  4. 方法名最後一個參數若是是Context,則beetl會傳入這個參數。
3.2.3. 使用模板文件做爲方法

能夠不用寫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'} 

也能夠在functions創建子目錄,這樣function則具備namespace,其值就是文件夾名

3.3. 自定義格式化函數

須要實現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); 

3.4. 自定義標籤

標籤形式有倆種,一種是標籤函數,第二種是html tag。第二種實際上在語法解析的時候會轉化成第一種,其實現是HTMLTagSupportWrapper,此類將會尋找root/htmltag目錄下同名的標籤文件做爲模板來執行。相似普通模板同樣,在此就不詳細說了

3.4.1. 標籤函數

標籤函數相似jsp2.0的實現方式,須要實現Tag類的render方法便可

public class DeleteTag extends Tag{ @Override public void render(){ // do nothing,just ignore body ctx.byteWriter.write("被刪除了,付費能夠看") } } 

如上一個最簡單的Tag,將忽略tag體,並輸出內容

public class XianDeDantengTag extends Tag{ @Override public void render(){ doBodyRender(); } } 

此類將調用父類方法doBodyRender,渲染tag body體

public class CompressTag extends Tag{ @Override public void render(){ BodyContent content = getBodyContent(); String content = content.getBody(); String zip = compress(cotnent); ctx.byteWriter.write(zip); } } 

此類將調用父類方法getBodyContent ,得到tag body後壓縮輸出

tag類提供了以下屬性和方法供使用

  • args 傳入標籤的參數
  • gt GroupTemplate
  • ctx Context
  • bw 當前的輸出流
  • bs 標籤體對應的語法樹,不熟悉勿動

3.5. 自定義虛擬屬性

能夠爲特定類註冊一個虛擬屬性,也能夠爲一些類註冊虛擬屬性

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

    User類的全部虛擬屬性將執行eval方法,此方法根據年紀屬性來輸出對應的描述。

  • public void registerVirtualAttributeEval(VirtualAttributeEval e) 爲一些類註冊須要屬性,VirtualAttributeEval.isSupport方法將判斷是否應用虛擬屬性到此類

    以下是虛擬屬性類的定義

    public interface VirtualClassAttribute{ public Object eval(Object o, String attributeName, Context ctx); } public interface VirtualAttributeEval extends VirtualClassAttribute{ public boolean isSupport(Class c, String attributeName); } 

3.6. 使用額外的資源加載器

某些狀況下,模板來源不止一處,GroupTemplate配置了一個默認的資源加載器,若是經過gt.getTemplate(key),將調用默認的ResourceLoader,獲取模板內容,而後轉化爲beetl腳本放入到緩存裏。你也能夠傳入額外的資源管理器加載模板,經過調用gt.getTemplate(key,otherLoader)來完成;

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

對於更復雜的模板資源來源,也能夠自定義一個資源加載來完成,參考下一節

3.7. 自定義資源加載器

若是模板資源來自其餘地方,如數據庫,或者混合了數據庫和物理文件,或者模板是加密的,則須要自定義一個資源加載器。資源加載器須要實現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); } } } 

Resource類須要實現OpenReader方法,以及isModified方法。對於模板內容存儲在數據庫中,openReader返回一個Clob,isModified 則須要根據改模板內容對應的lastUpdate(一般數據庫應該這麼設計)來判斷模板是否更改

public abstract class Resource{ /** * 打開一個新的Reader * * @return */ public abstract Reader openReader(); /** * 檢測資源是否改變 * * @return */ public abstract boolean isModified(); 

參考例子能夠參考beetl自帶的ResourceLoader

3.8. 使用CompositeResourceLoader

組合加載器,能夠包含多個已有的ResourceLoader,以下代碼將建立一個包含倆個文件和內存的ResourceLoader

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

如上例子,groupTemplate從CompositeResourceLoader里加載/xxx.html,因爲http:和db:前綴都不匹配,所以,將實際採用fileLoader1加載path1+/xxx.html,以下是xxx.html文件內容

<%
include("/xxx2.html"){} include("http:/xxx.html"){} %> 

第2行仍然是由fileLoader1加載,但第3行以http:前綴開頭,所以將fileLoader2加載path2+/xxx.html.xxx.html內容以下

<%
include("db:1"){} %> 

由於以db:開頭,所以會採用MapResourceLoader加載,內容是key爲db:1對模板

3.9. 自定義錯誤處理器

錯誤處理器須要實現ErrorHandler接口的processExcption(BeetlException beeExceptionos, Writer writer);

  • beeExceptionos,模板各類異常
  • writer 模板使用的輸出流。系統自帶的並未採用此Writer,而是直接輸出到控制檯

自定義錯誤處理多是有多個緣由,好比

  1. 想將錯誤輸出到頁面而不是控制檯

  2. 錯誤輸出美化一下,而不是自帶的格式

  3. 錯誤輸出的內容作調整,如不輸出錯誤行的模板內容,而僅僅是錯誤提示

  4. 錯誤輸出到日誌系統裏

  5. 不只僅輸出日誌,還拋出異常。默認自帶的不會拋出異常,ReThrowConsoleErrorHandler 繼承了ConsoleErrorHandler方法,打印異常後拋出

    public class ReThrowConsoleErrorHandler extends ConsoleErrorHandler{ @Override public void processExcption(BeetlException ex, Writer writer){ super.processExcption(ex, writer); throw ex; } } 

beetl 提供 ErrorInfo類來wrap BeetlException,轉化爲較爲詳細的提示信息,他具備以下信息

  • type 一個簡單的中文描述
  • errorCode 內部使用的錯誤類型標識
  • errorTokenText 錯誤發生的節點文本
  • errorTokenLine 錯誤行
  • msg 錯誤消息,有可能沒有,由於有時候errorCode描述的已經很清楚了
  • cause 錯誤的root 異常,也可能沒有。

BeetlException 也包含了一個關鍵信息就是 resourceId,即出錯所在的模板文件

3.10. 自定義安全管理器

全部模板的本地調用都須要經過安全管理器校驗,默認須要實現NativeSecurityManager 的public boolean permit(String resourceId, Class c, Object target, String method) 方法

以下是默認管理器的實現方法

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

3.11. 註冊全局共享變量

groupTemplate.setSharedVars(Map<String, Object> sharedVars)

3.12. 佈局

佈局能夠經過Beetl提供的include,layout 以及模板變量來完成。模板變量能完成複雜的佈局

  • 採用layout include

    <%
       //content.html內容以下: layout("/inc/layout.html"){ %> this is 正文 .......... <% } %> 

    如上一個子頁面將使用layout佈局頁面,layout 頁面內容以下

    <% include("/inc/header.html"){} %> this is content:${layoutContent} this is footer: 

    layoutContent 是默認變量,也能夠改爲其餘名字,具體請參考layout標籤函數

    全局變量老是能被佈局用的頁面所使用,若是佈局頁面須要臨時變量,則須要顯示的傳入,如:

    <%
      var user= model.user; include("/inc/header.html",{title:'這是一個測試頁面',user:user}){} %> 

    這樣,title和user成爲全局變量,能被header.html 及其子頁面引用到

  • 繼承佈局:採用模板變量和include

    <% var jsPart = { %> web頁面js部分 <% }; %> <% var htmlPart = { %> web頁面html部分 <% }; include("/inc/layout.html",{jsSection:jsPart,htmlSection:htmlPart}){} %> 
    layout.html頁面以下: 
    <body> <head> ${jsSection} </head> <body> ....... ${htmlSection} </body> 

3.13. 性能優化

Beetl性能已經很快了,有些策略能更好提升性能

  • 使用二進制輸出,此策略可使模板在語法分析的時候將靜態文本轉化爲二進制,省去了運行時刻編碼時間,這是主要性能提升方式。但須要注意,此時須要提供一個二進制輸出流,而不是字符流,不然性能反而降低
  • 使用FastRuntimeEngine,默認配置。 此引擎能對語法樹作不少優化,從而提升運行性能,如生成字節碼來訪問屬性而不是傳統的反射訪問。關於引擎,可能在新的版本推出更好的引擎,請隨時關注。
  • 經過@type 來申明全局變量類型,這不能提升運行性能,但有助於模板維護
  • 自定義ResourceLoader的isModified必須儘快返回,所以每次渲染模板的時候都會調用此方法

爲何Beetl性能這麼好…………(待續)

3.14. 分佈式緩存模板

Beetl模板引擎模板在同一個虛擬機裏緩存Beetl 腳本。也能夠將緩存腳本到其餘地方,只要實現Cache接口,並設置ProgramCacheFactory.cache便可,這樣GroupTemplate將從你提供的Cache中存取Beetl腳本

此功能未被很好測試

3.15. 定製輸出

佔位符輸出容許定製。如全部日期類型都按照某個格式化輸出,而不需顯式的使用格式化輸出,或者爲了防止跨腳本站點攻擊,須要對類型爲String的值作檢查等,沒必要使用格式化函數,能夠直接對佔位符輸出進行定製,代碼以下

PlaceholderST.output = new PlaceholderST.Output(){ @Override public void write(Context ctx, Object value) throws IOException { //定製輸出 ctx.byteWriter.writeString("ok"+value!=null?value.toString:""); } }; 

若是PlaceholderST靜態變量output 不爲null,將使用output 來輸出

3.16. 定製模板引擎

Beetl在線體驗(http://ibeetl.com/beetlonline/)面臨一個挑戰,容許用戶輸入任何腳本作練習或者分享代碼。但又須要防止用戶輸入惡意的代碼,如

<%
for(var i=0;i<10000000;i++){ //其餘代碼 } %> 

此時,須要定製模板引擎,遇到for循環的時候,應該限制循環次數,譬如,在線體驗限制最多循環5次,這是經過定義替換GeneralForStatement類來完成的,這個類對應了for(exp;exp;exp) ,咱們須要改爲以下樣子:

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

儘管上面代碼很複雜,但其實是改寫了原來的GeneralForStatement,將原來的24行while(true) 替換成for (; i < 5; i++) 用來控制最大循環,而且62行檢測若是循環退出後,i等於5,則提示Too Many Data in Loop.

如今須要將此類替換原有的GeneralForStatement,

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

繼承FastRuntimeEngine有所不一樣,由於改引擎會copy出一個腳本作分析優化,所以,倆個腳本都須要作修改

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

該監聽器返回一個新的RestrictForStatement 類,用來替換來的GeneralForStatement。若是返回null,則不需替換。這一般發生在你僅僅經過修改該類的某些屬性就能夠的場景

完成這些代碼後,在配置文件中申明使用新的引擎

ENGINE=org.bee.tl.online.VarRefTemplateEngine 

這樣就完成了模板引擎定製。

另一種定製模板引擎方法(2.7.22)

在2.7.21 版本後,提供了另一種定製模板引擎的方法,能夠在Beetl語法樹生成的時候提供定製(上面那種是在生成後),這種方法更靈活。但須要對語法樹有所瞭解。

首先須要建立一個引擎

ENGINE=org.bee.tl.online.VarRefTemplateEngine 

OnlineTemplateEngine 代碼以下,

public class VarRefTemplateEngine extends DefaultTemplateEngine { protected AntlrProgramBuilder getAntlrBuilder(GroupTemplate gt){ AntlrProgramBuilder pb = new AntlrProgramBuilder(gt); return pb; } class VarRefAntlrProgramBuilder extends AntlrProgramBuilder{ public VarRefAntlrProgramBuilder(GroupTemplate gt) { super(gt); } } } 

AntlrProgramBuilder 方法用於構造語法樹,有多個Protected方法能夠重載,以實現新的實現。

3.17. 直接運行Beetl腳本

Beetl模板本質上會轉化爲Beetl腳原本執行,這點跟jsp轉爲servlet來執行相似。GroupTemplate提供方法能夠直接執行Beetl腳本

  • public Map runScript(String key, Map<String, Object> paras) throws ScriptEvalError
  • public Map runScript(String key, Map<String, Object> paras, Writer w) throws ScriptEvalError
  • public Map runScript(String key, Map<String, Object> paras, Writer w, ResourceLoader loader) throws ScriptEvalError

key爲資源名,paras爲腳本的全局變量,w可選參數,若是執行腳本有輸出,則輸出到w裏,loader參數可選,若是指定,則使用此laoder加載腳本

執行腳本完畢後,返回到Map裏的值可能包含以下:

  • 模板的頂級的臨時變量,key爲臨時變量名
  • return 值將返回到map裏 ,key爲return

以下腳本(此時就不須要腳本定界符了)

var a = 1; var b = date(); var c = '2'; return a+1; 

調用runScript後,map裏將返回key分別爲a,b,c,return。 值分別爲1,當前日期,字符串'2,以及3。

3.18. 模板校驗

GroupTemplate 提供了validateTemplate和 validateScript方法用來校驗模板,若是模板或者腳本有語法錯誤,則返回BeetlException,BeetlException包含了錯誤的具體信息,能夠參考ConsoleErrorHandler來了解如何處理異常,以下是一個簡單的處理片斷

BeetlException ex = groupTemplate.validateTemplate("/index.html"); if(ex==null){ return } ErrorInfo error = new ErrorInfo(ex); int line = error.getErrorTokenLine(); String errorToken = error.getErrorTokenText(); String type = error.getType(); 

4. Web集成

4.1. Web提供的全局變量

Web集成模塊向模板提供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); } } 

這樣,每次在模板裏均可以訪問變量sysVersion了,不須要在controller裏設置,或者經過servlet filter來設置

4.2. 集成技術開發指南

Beetl默認提供了WebRender用於幫助web集成開發,全部內置的集成均基於此方法。若是你認爲Beetl內置的各個web框架集成功能不夠,你能夠繼承此類,或者參考此類源碼從新寫,其代碼以下

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

4.3. Servlet集成

只須要在Servlet代碼裏引用ServletGroupTemplate就能集成Beetl,他提供了一個render(String child, HttpServletRequest request, HttpServletResponse response)方法。例子以下:

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

ServletGroupTemplate同其餘web集成同樣,將讀取配置文件來配置,若是須要經過代碼配置,能夠在Servlet listener裏 ServletGroupTemplate.instance().getGroupTemplate()方法獲取GroupTemplate

4.4. SpringMVC集成

須要作以下配置便可

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

同其餘集成方式同樣,模板的配置將放在beetl.properties中。

若是想獲取GroupTemplate,能夠調用以下代碼

BeetlGroupUtilConfiguration config = (BeetlGroupUtilConfiguration) this.getApplicationContext().getBean("beetlConfig"); GroupTemplate group = config.getGroupTemplate(); 

Controller代碼以下:

@RequestMapping(value = "/", method = RequestMethod.GET) public ModelAndView index(HttpServletRequest req) { ModelAndView view = new ModelAndView("/index"); //total 是模板的全局變量,能夠直接訪問 view.addObject("total",service.getCount()); return view; } 

http://git.oschina.net/xiandafu/springbeetlsql 有完整例子

一般能夠把模板放到WEB-INF目錄下,除了能夠配置beetl.propertis 外,還可使用Spring配置

<bean id="beetlConfig" class="org.beetl.ext.spring." init-method="init"> <property name="root" value="/WEB-INF/templates"/> </bean> 

4.5. SpringMVC集成高級

spring集成還容許註冊被spring容器管理的Function,Tag等,也容許配置多個視圖解析器等功能

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

如上圖所示,BeetlGroupUtilConfiguration有不少屬性,列舉以下

  • configFileResource 屬性指定了配置文件所在路徑,若是不指定,則默認在classpath下
  • functions 指定了被spring容器管理的function,key爲註冊的方法名,value-ref 指定的bean的名稱
  • functionPackages,指定了被spring容器管理的functionPackage,key爲註冊的方法包名,value-ref 指定的bean的名稱
  • tagFactorys ,註冊tag類,key是tag類的名稱,value-ref指向一個org.beetl.ext.spring.SpringBeanTagFactory實例,該子類是一個Spring管理的Bean。屬性name對應的bean就是tag類。須要注意,因爲Tag是有狀態的,所以,必須申明Scope爲 "prototype"。如代碼:
@Service @Scope("prototype") public class TestTag extends Tag { } 
  • typeFormats: 同functions,參數是 Map<Class<?>, Format>,其中key爲類型Class
  • formats:同functions,參數是 Map<String, Format>,其中key爲格式化函數名
  • virtualClassAttributes 同functions,參數Map<Class<?>, VirtualClassAttribute>,其中key爲類型Class
  • virtualAttributeEvals ,類型爲List
  • resourceLoader,資源加載器 ,值是 實現ResourceLoader的一個Bean
  • errorHandler ,錯誤處理,值是實現ErrorHandler的一個Bean
  • sharedVars,同functions,類型是Map<String, Object>,能夠在此設置共享變量
  • configProperties,類型是Properties,能夠覆蓋配置文件的某些屬性

以下配置,指定了三個視圖解析器,一個用於beetl頁面渲染,一個用於cms,採用了beetl技術,另一個是一些遺留的頁面採用jsp

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

Beetl視圖解析器屬性同spring自帶的視圖解析器同樣,支持contentType,order,prefix,suffix等屬性。

注意視圖解析器裏的屬性viewNames,這個用於判斷controller返回的path到底應該交給哪一個視圖解析器來作。

  • 以/template開頭的是beetlViewResolver來渲染。
  • 以/cmstemplate是交給cmsBeetlViewResolver渲染。
  • 若是都沒有匹配上,則是jsp渲染

你也能夠經過擴展名來幫助Spring決定採用哪一種視圖解析器,好比

<property name="viewNames"> <list> <value>/**/*.btl</value> </list> </property> 

若是你想更改此規則,你只能增長canHandle方法指定你的邏輯了。詳情參考org.springframework.web.servlet.view.UrlBasedViewResolver.canHandle

對於僅僅須要redirect和forward的那些請求,須要加上相應的前綴

  • 以"redirect:"爲前綴時:表示重定向,不產生BeetlView渲染模版,而直接經過Servlet的機制返回重定向響應.redirect:前綴後面的內容爲重定向地址,能夠採用相對地址(相對當前url),絕對地址(完整的url),若是採用/開頭的地址,會自動的在前面接上當前Web應用的contextPath,即contextPath爲test的Web應用中使用redirect:/admin/login.html 實際重定向地址爲 /test/admin/login.html
  • 以"forward:"爲前綴時:表示轉發,不產生BeetlView渲染模版。而是直接經過Servlet的機制轉發請求(關於轉發和重定向的區別,請自行查看Servlet API) forward:前綴後面的內容爲轉發地址,通常都是以/開頭相對於當前Web應用的根目錄

其餘集成須要注意的事項:

  • spring集成,請不要使用spring的 前綴配置,改用beetl的RESOURCE.ROOT 配置,不然include,layout會找不到模板
  • 若是根目錄不是默認目錄,能夠經過添加root屬性
<bean name="cmsbeetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"> <property name="root" value="/WEB-INF/views"/> </bean> 

4.6. Spring Boot集成

<dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl-framework-starter</artifactId> <version>1.1.55.RELEASE</version> </dependency> 

starter 自動處理以btl結尾的視圖,模板根目錄是Spring Boot默認的templates目錄。以下配置能夠修改beetl部分屬性

  • beetl-beetlsql.dev,默認爲true,即自動檢查模板變化
  • beetl.enabled 默認爲true,集成beetl。
  • beetl.suffix 默認爲btl,表示只處理視圖後綴爲btl的模板,好比controller裏代碼是「return /common/index.btl」,則能被Beetl處理,你寫成"return /common/index",或者"/common/index.html",都會出現404錯誤。

Starter能夠實現BeetlTemplateCustomize來定製Beetl

@Configuration public MyConfig{ @Bean public BeetlTemplateCustomize beetlTemplateCustomize(){ return new BeetlTemplateCustomize(){ public void customize(GroupTemplate groupTemplate){ } }; } } 

使用Starter來配置已經夠用,若是你想本身配置模板引擎, 經過java config來配置 beetl須要的BeetlGroupUtilConfiguration,和 BeetlSpringViewResolver,參考代碼以下

@Configuration public class BeetlConf { @Value("${beetl.templatesPath}") String templatesPath;//模板根目錄 ,好比 "templates" @Bean(name = "beetlConfig") public BeetlGroupUtilConfiguration getBeetlGroupUtilConfiguration() { BeetlGroupUtilConfiguration beetlGroupUtilConfiguration = new BeetlGroupUtilConfiguration(); //獲取Spring Boot 的ClassLoader ClassLoader loader = Thread.currentThread().getContextClassLoader(); if(loader==null){ loader = BeetlConf.class.getClassLoader(); } beetlGroupUtilConfiguration.setConfigProperties(extProperties);//額外的配置,能夠覆蓋默認配置,通常不須要 ClasspathResourceLoader cploder = new ClasspathResourceLoader(loader, templatesPath); beetlGroupUtilConfiguration.setResourceLoader(cploder); beetlGroupUtilConfiguration.init(); //若是使用了優化編譯器,涉及到字節碼操做,須要添加ClassLoader beetlGroupUtilConfiguration.getGroupTemplate().setClassLoader(loader); return beetlGroupUtilConfiguration; } @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; } } 

注意:這裏並無配置後綴,所以controller代碼裏必須顯式的加上後綴

//return "/hello" 錯誤用法 return "hello.html" 

注意,能夠經過Application.properties 配置以下屬性禁用BeetlSQL或者禁用Beetl

beetlsql.enabled=false beetl.enabled=false 

4.7. Jodd集成

須要配置web.xml,將全部請求交給jodd處理,參考:http://jodd.org/doc/madvoc/setup.html

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

MyWebApplication 和 MyAutomagicMadvocConfigurator 須要本身參照以下例子寫一個,前者用來設置beetl做爲視圖渲染,後者配置Jodd不要掃描beetl struts集成裏引用的struts類

public class MyAutomagicMadvocConfigurator extends AutomagicMadvocConfigurator { public MyAutomagicMadvocConfigurator(){ super(); //不掃描beetl 裏jar文件裏的action和result,不然,會掃描StrutsResultSupport不相干的class this.rulesJars.exclude("**/*beetl*.jar"); } } 
public class MyWebApplication extends WebApplication{ @Override protected void init(MadvocConfig madvocConfig, ServletContext servletContext) { //設置默認 madvocConfig.setDefaultActionResult(BeetlActionResult.class); } } 

最後,能夠寫Action了,瀏覽器輸入/index.html,jodd將執行world方法,並渲染ok.html模板。若是你想配置GroupTemplate,正如其餘集成框架同樣,只須要寫一個beetl.properties 便可。

@MadvocAction public class IndexAction { @Out String value; @Action("/index.html") public String world() { value = "Hello World!"; return "/ok.html"; } } 

https://git.oschina.net/xiandafu/beetl-jodd-sample 有完整例子

4.8. JFinal3.0&JFinal2.o集成方案

Beetl提供 JFinal3.0 集成,使用JFinal3BeetlRenderFactory ,經過以下注冊便可使用beetl模板引擎

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能夠添加擴展函數,格式化函數,共享變量等, } 

業務邏輯代碼:

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

BeetlRenderFactory 默認使用FileResourceLoader ,其根目錄位於WebRoot目錄下,若是你須要修改到別的目錄,能夠設置配置文件,如

RESOURCE.root= /WEB-INF/template/ 

https://git.oschina.net/xiandafu/beetl-jfinal-sample 有完整例子,採用jfinal+beetl寫的一個博客系統

https://git.oschina.net/xiandafu/jfinal_beet_beetsql_btjson 同上,但DAO部分採用了BeetlSql

JFinal3 與 Jfinal2.0不兼容,且不像Appache Common Lang那樣,不兼容狀況下采用不一樣的包名,致使了Beetl不能同時兼容:若是想在beetl(2.7.10)之後版本中仍然使用Jfinal2,須要本身寫集成代碼,本身寫一個IMainRenderFactory的實現類

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

業務邏輯代碼:

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

而後在Jfinal2裏配置完成

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

4.9. Nutz集成

Nutz集成提供了 BeetlViewMaker ,實現了 ViewMaker方法,以下代碼

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

須要注意的是,若是使用了nutz的obj(http://www.nutzam.com/core/mvc/view.html),則須要在模板頂部申明obj是動態對象,如

<%
directive dynamic obj
%>  ${obj.user.title} ${obj.user.name} 

或者使用beetl的默認引擎,採起以下配置

ENGINE=org.beetl.core.engine.DefaultTemplateEngine

4.10. Struts2集成

須要在struts2配置文件裏添加result-types作以下配置

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

該類會根據struts配置文件獲取模板,如上例的hello.html,並將formbean的屬性,以及request屬性做爲全局變量傳遞給模板

https://git.oschina.net/xiandafu/beetl-struts2-sample 有完整例子

Struts2.5 自己作了包名調整,所以自從Beetl2.8.0之後,只支持Struts2.5.x以上版本,這個版本安全漏洞少.... :)

4.11. MVC分離開發

對於web應用來講,必須經過controller才能渲染模板,beetl也能夠寫完模板後,在未完成controller狀況下,直接渲染模板 此方法既能夠做爲一般的全棧式開發人員使用,也能夠用於前端人員單獨開發模板用。 Beetl使用WebSimulate來模擬模板渲染或者REST請求返回json數據,WebSimulate 會取出請求路徑,而後執行values目錄下同一個請求路徑的腳本,腳本的頂級變量都將做爲全局變量,並渲染請求路徑同名的的模板文件。 好比請求路徑是http://127.0.0.1:8080/user/userlist.html, 則WebSimulate會執行/values/user/userlist.html.var 腳本,獲取到全部頂級變量,並渲染/user/userlist.html 頁面 若是腳本定義了名爲json的變量,則WebSimulate 返回的是json數據,不然,則是模板渲染 若是腳本里還定義了ajax變量,則認爲是局部渲染,ajax變量由於字符串,就是表明ajaxId WebSimulate容許使用path變量,且在values目錄下,用$$代替,好比對於REST請求 /user/1,若是在values目錄下有/values/users/$$.var, 則能匹配上此模擬腳本 WebSimulate對應到腳本的時候,容許根據HTTP METHOD對應,好比一個REST的GET請求 /user/1,能夠對應/values/user/$$.get.var 對應的關係,老是精確匹配優先,對於/user/1,優先精確匹配/user/1.var,其次是/user/$$.get.var, 最後纔是/user/$$.var 則WebSimulate 在執行腳本的時候,老是先讀取/values/common.var, 以得到須要的公共變量

安裝WebSimulate較爲簡單,以springboot爲例子

@Controller @RequestMapping("/simulate") public class SimulateController { @Autowired WebSimulate webSimulate; @RequestMapping("/**/*.html") public void simluateView(HttpServletRequest request,HttpServletResponse response){ webSimulate.execute(request, response); } @RequestMapping("/api/**") public void simluateJson(HttpServletRequest request,HttpServletResponse response){ webSimulate.execute(request, response); } } 

如上,全部以/smulate 開頭的請求,都會使用模擬數據來支持分離開發,其中simluateView來模擬視圖渲染,simluateJson來模擬REST請求的數據

WebSimulate 初始化代碼以下

@Bean public WebSimulate getWebSmulate(BeetlSpringViewResolver resolver){ WebSimulate webSimulate = new WebSimulate(resolver.getConfig().getGroupTemplate()){ public String getValuePath(HttpServletRequest request){ return this.removePreffix( request.getServletPath()); } protected String getRenderPath(HttpServletRequest request) { return this.removePreffix( request.getServletPath()); } private String removePreffix(String path){ return path.replaceFirst("/simulate", ""); } }; return webSimulate; } 

WebSimulate 一般能夠直接使用,但本例子中,爲了徹底模擬,須要去掉/simulate",這樣沒必要要建立一個/values/simulate

如上配置完畢,若是普通模板請求

/simulate/user/userlist.html

將會執行/values/user/userlist.html.var 的腳本,好比,模擬users數據

var users = [{"name":"xiandafu"},{"name":"lucy"}]; 

若是一個REST請求

/simulate/api/user/1

能夠建立以下文件/values/api/user/$$.get.var,內容直接返回一個json字符串

var json = "{'success':true}"; 

WebSimulate 構造的時候須要一個實現JsonUtil的類(Beetl並不自帶json序列化工具),這樣,對於要返回的json數據,能夠沒必要向上面的例子那樣,返回json字符串,能夠返回一個對象,如Map,而後交給jsonUtil來序列化返回客戶端

腳本自己能夠獲取模擬請求的參數,如session,parameter等,從而靈活的模擬數據,具體請參考WebSimulate源碼

直接訪問模板前提是使用了僞模型,這與實際的項目採用的模型並不一致,所以當模板採用僞模型驗證後,須要重啓web應用,才能使用真正的模型去測試,不然,模板引擎會報錯,這是由於beetl默認的FastRuntimeEngine會根據模型優化模板,對同一個模板不一樣的模型會報錯,除非採用DefaultTemplateEngine 或者頁面申明類型變量是動態的。

4.12. 整合ajax的局部渲染技術

愈來愈多web網站依賴於ajax,如table的翻頁,流行方式是瀏覽器發出ajax請求,後臺處理後返回一個json,瀏覽器端將json數據拆開,拼成一條一條的行數據,而後生成dom節點,追加到表格裏。 做爲另一種可選技術,beetl支持局部渲染技術,容許後臺處理返回的是一個完成的html片斷,這樣,前端瀏覽器能夠直接將這個html片斷追加到表格裏。在我作的性能測試裏,倆種方式性能差異不大(http://bbs.ibeetl.com/ajax//)

好比模板index.html有不少動態內容,有動態生成的菜單,有右側的top10,也有核心區域的表格,大概內容以下

<#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片斷結尾 } %> 

#ajax 用於告訴告訴模板引擎,此處是個局部渲染標記,標記爲"userTable",對於正常渲染視圖"index.html"頁面,#ajax標記沒什麼用處,table仍能獲得正常渲染。若是渲染的視圖是index.html#userTable,則模板只會渲染#ajax標記得模板片斷,其餘部分將忽略。關於完整例子,能夠參考https://git.oschina.net/xiandafu/beetlajax

後臺代碼以下:

render("/index.html#userTable"); 

只須要在模板路徑後加上#就表示渲染的並不是是整個模板,而是模板的一部分,這一部分由#後面的標記來標示

ajax 片斷渲染也支持默認狀況下不渲染,僅僅作爲一個片斷使用,如一個頁面有許多後臺交互操做,並返回相應的html片斷,能夠將這些html片斷也放到同一個模板裏,使用ajax norender,表示渲染整個模板的時候默認並不須要渲染此ajax片斷

<%
<html>

</html>
#ajax norender success: { %> <div id="success"> 操做成功 </div> <% } %>  #ajax norender failure: { %> <div id="failure"> 操做失敗 </div> <% } %> 

這樣,此頁面默認狀況下並無輸出success,和 failure片斷

注意,Ajax片斷本質上是從模版的ajax標記處開始渲染,所以,ajax須要的變量在模版裏也必須是全局變量,若是你只是個局部變量,beetl會報出找不到變量,即便你binding了這個變量,beetl也認爲這個是局部變量,如

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

符號#ajax 實際上用來標記一個模板渲染片斷,它還有個別名的叫#fragment,二者是同樣的,好比

<%
#fragment part2:{ println("part2"); } %> 

4.13. 在頁面輸出錯誤提示信息

2.2.3版本之後,新增長org.beetl.ext.web.WebErrorHandler,能夠在web開發的時候在頁面輸出提示信息,在產品模式下在後臺輸出提示信息(經過配置屬性ESOURCE.autoCheck= true來認爲是開發模式),僅僅須要配置以下:

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() 便可
  • 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();
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,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
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函數並無內置,須要註冊,以下

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

spel(spelString, rootObject) SpEL方法傳入一個Spring SpEL表達式以獲取表達式結果,方法建議以函數的方式定義在BeetlGroupUtilConfiguration的functions中

spelString: SpEL表達式字符串,必傳(不然返回null) rootObject: 做爲spel的根對象(對應#root),能夠是一個Map或Bean對象,默認取空Map。因爲Beetl運行上下文沒法直接獲取模版局部變量的變量名,建議局部變量採用自定義Map的方式傳入

  • 列表篩選(以自定義Map爲根對象傳入局部變量)

    <% var intArray = [12, 1, 2, 3]; %>
    ${spel('#root.intArray.?[#this>10]', {intArray: intArray})} 
  • 以Bean對象爲根對象

    <% var now = date(); %>
     ${spel('#root.year + 1900', now)} 
  • 直接new對象

     ${spel('(new java.util.Date()).year + 1900')} 
  • 直接引用Spring Bean

    ${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內置的一些功能,如

// 測試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(\<url>, \<method>) 對應類: org.beetl.ext.spring.AccessUrlIfFunction 參數: url: 字符串表示的測試URL Path,不須要指定Context Path,缺省會直接返回true method: 字符串表示的訪問方式, 默認爲GET, 建議全大寫 返回值: 測試當前登陸用戶是否能訪問指定的URL Path, 返回true or false

    示例:

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

    若是當前環境不存在Spring Security安全上下文,將返回true 若是當前環境不存在用戶認證憑證,做爲匿名登陸進行測試

  • expIf(\<exp>) 對應類: org.beetl.ext.spring.AccessExpressionIfFunction 參數: exp: Spring Security安全表達式,缺省會直接返回true 返回值: 測試當前登陸用戶是否知足指定的安全表達式,返回true or false 示例:

    expIf('isAuthenticated()') 

    若是當前環境不存在Spring Security安全上下文,將返回true 若是當前環境不存在用戶認證憑證,做爲匿名登陸進行測試

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

    <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一個模板,如 :

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

    若是想往子模板中傳入參數,則能夠後面跟一個json變量

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

    這樣user,和id 能夠在header.html被引用,併成爲header.html的全局變量

    (beetl1.2 也叫includeFileTemplate ,2.0仍然支持,但再也不文檔裏體現了)

  • layout 提供一個佈局功能,每一個頁面老是由必定佈局,如頁面頭,菜單,頁面腳,以及正文。 layout標籤容許爲正文指定一個佈局,以下使用方式

    content.html內容以下:

    <%
      //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標籤的內容,並指定多長時間刷新,如

    <% :cache('key2',10,false){ %> 內容體 <% } %> 

    須要指定三個參數

    • 第一個是cache的Key值
    • 第二個是緩存存在的時間,秒爲單位
    • 第三個表示是否強制刷新,false表示不,true表示強制刷新

      Cache默認實現org.beetl.ext.tag.cache.SimpleCacheManager. 你能夠設置你本身的Cache實現,經過調用CacheTag. cacheManager= new YourCacheImplementation();

      能夠在程序裏調用以下方法手工刪除Cache:

      public void clearAll(); public void clearAll(String key); public void clearAll(String... keys); 
  • includeJSP,能夠在模板裏包括一個jsp文件,如:

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

    key value 都是字符串,將以parameter的形式提供給jsp,所以jsp能夠經過request.getParameter("key")來獲取參數

    主要注意的是,這個標籤並不是內置,須要手工註冊一下

    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 配置使用了前綴,錯誤:
<property name="prefix" value="/WEB-INF/view/"></property> 

能夠指定模板根目錄

<bean name="beetlConfig" class="org.beetl.ext.spring.BeetlGroupUtilConfiguration" init-method="init"> <property name="root" value="/WEB-INF/beetl.properties"/> 
  • spring 視圖名使用了相對路徑,錯誤

    return "userDetail.btl" 

    應該使用以下

    return "/user/user.btl" 
  • Spring Boot 自定義模板根目錄

若是模板不在resources/templates目錄下,好比在resouces/pages/views下,應該用以下方式初始化

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默認使用了了以下引擎

ENGINE=org.beetl.core.engine.FastRuntimeEngine 

這個引擎會假設同一個模板裏的同一個全局變量應該類型惟一,若是你的模板是公共模板,類型不同,能夠在模板頂部使用dynamic,好比

<% directive dynamic xxx %> 

若是你的模板這種狀況不少,建議更換成默認引擎配置

ENGINE=org.beetl.core.engine.DefaultTemplateEngine 

還有種狀況是在Spring Boot 下出現,參考下一節

5.8.4 Spring Boot 出現 ClassCastException

請使用最新的Beetl版本,使用Starter或者參考Spring Boot集成一章集成Spring Boot。這是Spring Boot dev模式引發的問題

相關文章
相關標籤/搜索