Thymeleaf3自定義表示式
1. 簡介
在上一篇《Thymeleaf3自定義方言Dialect與處理器》中,記錄了使用Thymeleaf3自定義方言和處理器的方法,同時也說到了方言Dialect是處理器與表示式的集合。既然處理器支援自定義,那麼推測表示式應該也支援自定義的。但在Thymeleaf的官方tutorials以及article中均未能找到相關的指導與說明,若有人知道官方相關說明的位置,希望能留言或者別的方式告知一下。 既然官方tutorials沒有,那就baidu了一下,還是找到了網上各位大神的記錄文章。主要參考了《thymeleaf自定義工具物件》 這篇文章。 然後輔以我自己的實踐、理解,以及對官方原始碼的模仿,得到了一些新的理解,整理成了這篇文章。
2. 常用表示式Expression
在Thymeleaf自帶的 StandardDialect 和 Spring提供的 SpringStandardDialect 中,有很多常用的表示式Expression,例如 #strings
#calenders
等。
使用樣例如下,這些在官方的文件中都有詳細的介紹,此處就不再贅述。
/* * Null-safe toString() */ ${#strings.toString(obj)} // also array*, list* and set* /* * Check whether a String is empty (or null). Performs a trim() operation before check * Also works with arrays, lists or sets */ ${#strings.isEmpty(name)} ${#strings.arrayIsEmpty(nameArr)} ${#strings.listIsEmpty(nameList)} ${#strings.setIsEmpty(nameSet)}
3. 實現 IExpressionObjectFactory 介面
從IExpressionObjectFactory
的名稱上來看就是表示式物件的工廠類,即為創造提供表示式物件的介面。
建立實現類,大致如下:
public class BaseDialectExpressionFactory implements IExpressionObjectFactory {
public static final String DICTIONARY_UTILS_EXPRESSION_NAME = "dic";
public static final Set<String> ALL_EXPRESSION_OBJECT_NAMES;
static {
final Set<String> allExpressionObjectNames = new LinkedHashSet<String>();
allExpressionObjectNames.add(DICTIONARY_UTILS_EXPRESSION_NAME);
ALL_EXPRESSION_OBJECT_NAMES = Collections.unmodifiableSet(allExpressionObjectNames);
}
@Override
public Set<String> getAllExpressionObjectNames() {
return ALL_EXPRESSION_OBJECT_NAMES;
}
@Override
public Object buildObject(IExpressionContext context, String expressionObjectName) {
if(null == expressionObjectName)
return null;
Object obj = null;
switch (expressionObjectName) {
case DICTIONARY_UTILS_EXPRESSION_NAME:
obj = getDictionaryHelper();
break;
default:
break;
}
return obj;
}
@Override
public boolean isCacheable(String expressionObjectName) {
if(DICTIONARY_UTILS_EXPRESSION_NAME.equals(expressionObjectName)) {
return true;
}else {
return false;
}
}
private Object getDictionaryHelper() {
//建立物件的程式碼,省略~
}
}
那麼逐個方法來閱讀。
1. getAllExpressionObjectNames
方法名稱也表明了返回的是這個工廠類中全部支援的表示式的名稱。返回值是 ALL_EXPRESSION_OBJECT_NAMES
,它在static塊中被初始化為一個不可修改的set集合,因此儲存我們自定義的表示式名稱的變數也需要是一個static的變數DICTIONARY_UTILS_EXPRESSION_NAME
,賦值為 “dic”。
2. buildObject
方法簽名:
public Object buildObject(IExpressionContext context, String expressionObjectName)
還是看名稱,“建立物件”,同時介面的註釋是 Build the requested object 。
因此這個方法中,我們需要創造表示式具體對映到的處理類物件中。此處我對映到的是DictionaryHelper
類,提供瞭如下的方法
public String value(String dicName,String code) { .... }
public List<DictionaryItem> list(String code){ ... }
當完成全部程式碼之後,就可以在模板中通過表示式 ${dic.value('foo','bar')}
呼叫到 value(String dicName,String code)
方法; 表示式 ${dic.list('foo')}
呼叫到list(String code)
方法。
在具體的實際應用中,提供的工具類往往需要依賴別的上下文(例如Spring容器)來完成一些資料的查詢與轉換,例如,資料庫字典的翻譯等等。因此可能需要拿到spring容器,再取出工具類,又或者是工具類本身持有spring容器。
但我的demo專案裡面本身就是使用了spring進行管理,並且Thymeleaf與spring之間也有很多聯絡,那當使用spring之後,能否方便的獲取到spring容器呢?
經過一番查詢,找到了org.thymeleaf.spring4.context.SpringContextUtils
這個utils類,有方法返回ApplicationContext
物件:
public static ApplicationContext getApplicationContext(final ITemplateContext context) {
其中傳入的引數類 ITemplateContext
的上級介面是 IExpressionContext
,巧的是方法 buildObject
的第一個入參就是這個介面的物件,因此我產生了大膽的想法:通過 SpringContextUtils.getApplicationContext()
獲取到spring容器物件。
具體這麼寫:
ApplicationContext applicationContext =
SpringContextUtils.getApplicationContext((ITemplateContext) context);
在測試中,確實也獲取到了spring的容器,但還是存在問題的 ,這裡的操作是將 IExpressionContext
介面物件向下轉換成ITemplateContext
物件了。一般在沒有明確知道是可以向下轉換的時候,不會進行強制轉換的。此時,我也沒有具體瞭解傳入的IExpressionContext
物件是如何產生的。所以,此處僅僅是提供一個思路,請在弄清了相關問題之後再嘗試使用
3. isCacheable
public boolean isCacheable(String expressionObjectName)
還是顧名思義,設定是否可以快取。具體的快取物件以及快取有效範圍,我在結合原始碼註釋以及實踐測試之後的理解如下:
快取的是 buildObject
返回的物件,有效範圍是整個渲染整個模板的時候。例如,若在a.html模板中有多處使用到了${dic.value('foo','bar')}
表示式,在快取設定為false的情況下,Thymeleaf引擎會多次呼叫buildObject
方法;在設定為true的情況下,Thymeleaf引擎就只會呼叫buildObject
一次。
考慮到執行緒安全或者是結合具體業務場景下,就並不是所有的表示式都適合使用快取,因此可以根據入參判斷是否需要快取。
4. 註冊到自定義方言中
此處開始的描述,是在建立了自定義方言BaseDialect的前提上的
BaseDialect除了需要繼承抽象類AbstractProcessorDialect
外,為了自定義表示式還需要實現介面IExpressionObjectDialect
,然後實現方法 getExpressionObjectFactory
,如下:
@Override
public IExpressionObjectFactory getExpressionObjectFactory() {
if (this.expressionObjectFactory == null) {
this.expressionObjectFactory = new BaseDialectExpressionFactory();
}
return this.expressionObjectFactory;
}
5. 註冊方言
SpringBoot+SpringMVC的註冊方式:
@Bean
@ConditionalOnMissingBean
public BaseDialect baseDialect() {
//增加Thymeleaf的方言,支援一些自定義的模板表示式
return new BaseDialect();
}
至此,自定義的表示式就定義完成了。
6. 具體使用
下面給出我自己的使用樣例:
<td align="center" nowrap="nowrap" th:text="${#dic.value('type123',order.status)}">
啟用
</td>
<select name="status" style="width:80px;">
<th:block th:with="recStats=${#dic.list('字典表的type')}">
<option th:if="${recStats}" th:each="rec:${recStats}"
th:value=${rec.value}>
[[${rec.valueDescription}]]
</option>
</th:block>
</select>