3. Beetl
寫在開頭,Beetl是由《Spring Boot 2精髓》作者所開發並維護的後端模板引擎,主要用於渲染視圖模板。
關於模板引擎,博主瞭解過的主要是JSP 和 FreeMarker,視圖渲染技術的瞭解並不多。
這裏談一下自己對於Web開發的理解:基於現在的Web開發環境,前後端分離開發的思想,相對後端來講,很多時候是面向接口編程,拿當下火熱的前端漸進式組件框架Vue來講,於後端的交互無異於請求數據並返回,前端框架進行數據解析和視圖渲染,後端進行接口數據的獲取以及返回。
藉此閱讀技術書籍以及結合案例Demo的機會,來比較系統的瞭解一下這個後端模板引擎:Beetl
關於模板介紹,可以移步:http://ibeetl.com/ ,這裏就不贅述了,直接上手實操。
-
依賴引入
在Spring Boot中,beetl-framework-starter 將自動配置以 btl 結尾的所有視圖,將自動使用 Beetl 渲染響應的 resources/templates 目錄下的視圖文件<dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl-framework-starter</artifactId> <version>1.1.57.RELEASE</version> </dependency>
-
設置定界符和佔位符
記得當初使用JSP、ftl在頁面上寫腳本的時候,定界符和佔位符的概念就瞭解過了。
有圖有真相:JavaWeb時期用過的JSP
SSM時期用過的FreeMarker
默認配置:
<% %> 作爲定界符,${ }作爲佔位符可以通過配置文件來設置定界符和佔位符,需要在 resources 目錄下創建 beetl.properties 文件
案例中給出了一種配置方式:佔位符使用:’${’ 和 ‘}’,定界符使用:’@’ 和 ‘回車換行’。DELIMITER_PLACEHOLDER_START=${ DELIMITER_PLACEHOLDER_END=} DELIMITER_STATEMENT_START=@ DELIMITER_STATEMENT_END=
-
配置Beetl
Beetl爲了提高渲染性能,會在模板渲染之後對語法解析結果進行緩存,每次渲染前都會對模板文件進行更新監測,因此這個地方會存在一次I/O操作,線上系統可以取消這個更新監測。application.properties文件中添加:beetl-beetlsql.dev = false
關於Web項目中 target目錄 沒有同步更新最新目錄文件以及資源的問題:IDEA不像Eclipse會自動將新保存的文件或目錄及其他資源更新到target目錄中,必須在pom.xml中設置
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.*</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.*</include> </includes> </resource> </resources> </build>
接下來,maven clean、build就可以了,這個問題和mapper.xml文件無法加載的問題一致,原因都是在resource文件夾不被加入build行列。
-
GroupTemplate
Beetl的核心是GroupTemplate,是一個重量級對象,實際使用的時候建議使用單例模式創建,創建GroupTemplate需要兩個參數,一個是模板資源加載器,一個是配置類。模板資源加載器Beetl內置了6種,分別是:
模板資源加載器 說明 StringTemplateResourceLoader 字符串模板加載器,用於加載字符串模板,如本例所示 FileResourceLoader 文件模板加載器,需要一個根目錄作爲參數構造,傳入getTemplate方法的String是模板文件相對於Root目錄的相對路徑 ClasspathResourceLoader 文件模板加載器,模板文件位於Classpath裏 WebAppResourceLoader 用於webapp集成,假定模板根目錄就是WebRoot目錄,參考web集成章 MapResourceLoader 可以動態存入模板 CompositeResourceLoader 混合使用多種加載方式 GroupTemplate創建過程:
// 模板資源加載器 StringTemplateResourceLoader resourceLoader = new StringTemplateResourceLoader(); // 配置類 Configuration cfg = Configuration.defaultConfiguration(); // 單例模式創建 GroupTemplate gt = new GroupTemplate(resourceLoader, cfg);
配置類:通過自動注入的方式注入GroupTemplate,@PostConstruct作用於Config方法上,會在Spring啓動階段調用此方法,完成對GroupTemplate的拓展。
@Configuration public class BeetlExtConfig { @Autowired GroupTemplate groupTemplate; @Autowired ApplicationContext applicationContext; @PostConstruct public void config() { } }
以下內容爲Beetl的基礎語法相關內容,該部分內容會進行提綱式的筆記整理,不會詳細講述語法相關內容。
-
變量
- 全局變量:在模板或者子模板中使用
- request內置對象通過attribute在模板中均可直接通過name來引用。
- Session提供了Session會話,這裏指的是HTTPSession。
- parameter,讀取用戶提交參數。
- ctxPath,Web應用中的ContextPath。在Spring Boot中,應用默認是 “/”。
- servlet,WebVariable的實例,包含 HTTPSession、HTTPServletRequest、HTTPServletResponse三個屬性。模板中可以通過 request、response、session 來引用。
- 所有groupTemplate的共享變量
- 局部變量:只能在當前模板中使用
- 變量類型同JavaScript類型一致,在Beetl中,任何與高精度數據進行算數運算後的結果會轉換爲BIgDecimal類型,對應Java中的BigDecimal類型。
- 共享變量
-
類似全局變量,可以在任何模板中使用,需要通過groupTemplate的API進行添加。
定義:在注入GroupTemplate對象的配置類中,config方法中進行設置: groupTemplate.getSharedVars().put("key", value); 使用:在任意末班中,通過佔位符進行使用: ${key}
-
- 模板變量
-
定義:變量的內容是模板對應的輸出
@var a = 1 ; @var template = { <span>${a}</span〉 @} 輸出:${template}
template是個變量,內容位於${ }中,內容是1。
模板變量可以用在後面的任何地方,Beetl使用模板變量來完成繼承佈局方式。
-
- 全局變量:在模板或者子模板中使用
-
表達式
- 算數表達式
-
支持 +、-、*、/、% 等表達式,變量類型與相應的Java類型一致。
-
需要用到高精度數代替Double時,需要在數字後面添加 “h”,對應Java中BigDecimal類型。
例如:@var a = 123.25785236h;
-
爲保證浮點計算準確,含有高精度數表達式的計算過程全部按照高精度進行計算
-
- 邏輯表達式
-
支持 >、<、==、!=、>=、<=、!、&&、||、?等條件表達式
-
三元表達式只考慮true情況時,可以進行簡化
例如: $var a = 1; ${a == 1? "bingo" : ""} ${a == 1? "bingo"} 上述兩行代碼效果一致,在簡化的三元表達式中,false情況時,自動賦值爲null。 Beetl佔位符對null值不做輸出。
-
- 算數表達式
-
控制語句
- 循環語句
- for…in [elsefor]、for(exp; exp; exp)、while(exp)
-
for…in 循環中可以得到循環的上下文信息,會自動創建一個變量名+LP後綴的變量,提供循環的上下文信息
例如: @for(user in userList!){ <span>${userLP.index}</span> @}
上面的例子中在表達式:user in userList 後添加了 “!”,來進行安全輸出。即若user不存在,或者爲空,不要報錯,不進入循環體。此外上下文對象userLP還有其他相關信息:
-
- for…in [elsefor]、for(exp; exp; exp)、while(exp)
屬性/方法 說明 userLP.index 當前索引值,從1開始 userLP.size 集合長度 userLP.first 是否是第一個 userLP.last 是否是最後一個 userLP.even 索引是否是偶數 userLP.odd 索引是奇數 - 條件語句
- if…else [else if]
- 加強版switch case:select-case
-
允許case中使用邏輯表達式
-
不需要每個case都break,默認遇到符合條件的case執行後退出
<% var b = 1; select { case b < 1, b > 10: print("out of range"); default: print("error") } %>
-
- try…catch
-
捕獲模板異常
<% try{ callOtherSystemView(); } catch (error) { print(err.message); } %>
-
- 循環語句
-
函數調用
常用函數 說明 舉例 print/pringln 輸出對象,若對象爲空,則不輸出 has 判斷是否具有這個全局變量 if(has(userList)){…} isEmpty 判斷變量或者表達式是否爲空 debug 在控制檯打印變量或者表達式,附帶所在文件模板路徑信息 date 日期函數,獲得當前日期 trim 截取一個日期或者數字類型並返回字符串 trim(126.18, 1) -> 126.18
trim(date(),‘yyyy’) -> 2018parseInt 將字符串或者number轉換爲對應類型 parseLong、parseDouble global 返回全局變量值,參數是字符串 var user = global(“user_” + i) cookie 返回指定的cookie對象 var userCookie = cookie(“user”) strutil.* 字符串系列函數 詳情參考Beetl使用手冊 array.* 集合相關函數 shiro.* 安全框架shiro的相關方法封裝 shiroExt類中有說明,並非內置函數 spring.* Spring框架中可以使用的函數 spel表達式 reg.* 正則表達式相關函數 這裏說一下用到過的Shiro.hasPermission()方法吧,首先Shiro安全框架是集成進項目中來的,並配置了ShiroConfig配置類。權限控制的方式上採用基於URL的形式進行控制,在視圖層,使用Beetl提供的ShiroExt中的hasPermission方法,對模板中的URL字符串進行權限認證,ShiroExt在這一過程中只是做了封裝操作,獲取到權限URL參數、和當前Subject,並將參數交給Shiro框架進行處理。
/** * 驗證當前用戶是否擁有指定權限,使用時與lacksPermission 搭配使用 * * @param permission 權限名 * @return 擁有權限:true,否則false */ public boolean hasPermission(String permission) { return getSubject() != null && permission != null && permission.length() > 0 && getSubject().isPermitted(permission); } /** * 獲取當前 Subject * * @return Subject */ protected static Subject getSubject() { return SecurityUtils.getSubject(); } // isPermitted是接口Subject下的一個實現方法 boolean isPermitted(String permission); // 視圖層使用 @if(shiro.hasPermission("/cta/add")){ <#button name="添加" icon="fa-plus" clickFun="Cta.openAddCta()"/> @}
-
格式化函數
允許在佔位符輸出的時候指定格式化函數來格式化輸出
格式化格式:${exp, formatName=“可選參數”}-
日期格式化(同SimpleDateFormat)
${date(), dataFormat = "yyyy-MM-dd HH:mm"}
-
數字格式化(同NumberFormat)
${0.345, numberFormat = "#.%"} 輸出:34.5%
爲什麼會這樣?詳情請移步:https://docs.oracle.com/javase/6/docs/api/java/text/DecimalFormat.html
-
-
以Java方式進行調用
可以在模板中以Java方式調用表達式,使用時必須在表達式前用"@"符號標識-
定界符中使用
<% var size = @a.size(); %>
-
佔位符中使用
${ @user.getUserName() }
-
需要注意的是:
-
對於直接Java調用,GroupTemplate是可以配置進制此功能的。
-
可以通過安全管理器配置Beetl不允許調用那些類。默認java.lang.Runtime 和 java.lang.Process不允許被調用。
-
按照Java規範書寫類名、方法名以及屬性名
-
內部類訪問機制:outerClass$innerClass
-
表達式是Java風格,參數依然是Beetl表達式。
${@user.sayHello(user.name)} user.sayHello 是Java調用 user.name 依然是Beetl表達式
-
-
-
標籤函數
-
功能等同於 jsp tag
-
layout:佈局標籤函數
@layout("/inc/layout.html", {title:'主題'}){ <p>Hello Beetl</p> @}
layout標籤會把標籤體{ }部分內容渲染出來之後,傳給layout指定的模板頁面,默認接收變量名爲layoutContent,同時攜帶title變量進行傳遞。
layout.html <title>${title}</title> <div> ${layoutContent} </div>
-
include:包含標籤函數
@include("/inc/header.html", {title:"page header"}){...} <div> // main content </div> @include("/inc/footer.html"){}
include標籤第一個參數是公共模板的路徑,其後可以接受一個map參數,map中的,每一項值都會傳遞給子模板作爲子模板的全局變量。
-
-
安全輸出
Beetl在變量表達式後面使用符號 “!” 來提醒Beetl此變量可能不存在,表達式將返回 “!” 後的表達式值,若沒有表達式,則返回null。@ var user = null; ${user.name!"無此人姓名信息"}
當user爲null時,或者user.name爲null時,返回 “無此人姓名信息”