1. 程式人生 > >Thymeleaf3自定義表示式

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>