Thymeleaf 自定義Dialect

前言

Thymeleaf 豐富的擴展性爲我們實現自定義的標籤實現了可能。這裏以創建數據脫敏標籤這個需求爲例,講解下如何實現自定義的dialect。

需求描述:
controller的model中我們有客戶的手機號信息:"phone": "13001234567"。
按照默認的寫法,要在頁面中展示手機號,HTML模板爲:

<p th:text="${phone}"></p>

如果不使用自定義dialect,這裏會將完整的手機號展示出來:

<p>13001234567</p>

接下來我們打算打造一個自定義方言:

<p masking:text="${phone}"></p>

實現如下的效果:

<p>13*******67</p>

除了首位2個字符全部替換爲星號是自定義dialect的默認行爲。除此之外,我們還可以使用正則表達式來定義替換規則。比如,除了前兩個字符,其餘的全替換爲星號:

<p masking:text="${phone}" masking:pattern="^.{2}(.*)$"></p>

下面一起來實現這個Thymeleaf自定義方言。

實現自定義標籤處理器

對於Thymeleaf方言,自定義標籤的處理邏輯是在標籤處理器定義的。
自定義標籤處理器需要實現AbstractAttributeTagProcessor 接口,標籤的處理邏輯在doProcess 方法中編寫。

數據脫敏標籤的處理器代碼如下所示:

public class DataMaskingDialectTagProcessor extends AbstractAttributeTagProcessor {

    private static final String TEXT_ATTRIBUTE = "text";

    private static final String PATTERN_ATTRIBUTE = "pattern";

    private static final String DEFAULT_PATTERN = "^.{2}(.*).{2}$";

    public DataMaskingDialectTagProcessor(String prefix) {
        // (1)
        super(TemplateMode.HTML, prefix, null, false, TEXT_ATTRIBUTE, true, 1000, true);
    }

    @Override
    protected void doProcess(ITemplateContext iTemplateContext, IProcessableElementTag iProcessableElementTag, AttributeName attributeName, String s, IElementTagStructureHandler iElementTagStructureHandler) {
        //s爲自定義屬性text的內容,如果s爲表達式,該函數可以獲取表達式的值
        final Object value = getExpressionValue(iTemplateContext, s);

        IAttribute patternAttribute = iProcessableElementTag.getAttribute(PATTERN_ATTRIBUTE);
        if (null == patternAttribute) {
            // 設置標籤的內容
            iElementTagStructureHandler.setBody(StringUtil.doMasking(value.toString(), DEFAULT_PATTERN), false);
        } else {
            String patternValue = iProcessableElementTag.getAttribute(PATTERN_ATTRIBUTE).getValue();
            iElementTagStructureHandler.setBody(StringUtil.doMasking(value.toString(), patternValue), false);
        }
    }
}

(1)處所示的構造函數的參數比較多,下面爲大家列出各個參數的具體含義:

  • templateMode: 模板模式,這裏使用HTML模板。
  • prefix: 標籤前綴。即xxx:text中的xxx。在此例子中prefix爲masking。
  • elementName:匹配標籤元素名。舉例來說如果是div,則我們的自定義標籤只能用在div標籤中。爲null能夠匹配所有的標籤。
  • prefixElementName: 標籤名是否要求前綴。
  • attributeName: 自定義標籤屬性名。這裏爲text。
  • prefixAttributeName:屬性名是否要求前綴,如果爲true,Thymeeleaf會要求使用text屬性時必須加上前綴,即masking:text。
  • precedence:標籤處理的優先級,此處使用和Thymeleaf標準方言相同的優先級。
  • removeAttribute:標籤處理後是否移除自定義屬性。

StringUtil類用來進行字符替換。它保留前後n位字符不變,中間部分替換爲字符個數相同的星號。

StringUtil.java中的方法doMasking的代碼:

public static String doMasking(String target, String patternString) {
    Pattern pattern = Pattern.compile(patternString);
    Matcher matcher = pattern.matcher(target);
    if (matcher.matches()) {
        if (matcher.groupCount() < 1) {
            return target;
        }
        String group = matcher.group(1);
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < group.length(); i++) {
            stringBuilder.append("*");
        }
        return target.replace(group, stringBuilder.toString());
    }
    return target;
}

如果標籤中masking:text的屬性值是普通字符串的話,這個 自定義方言已經能夠滿足要求了。但是,text屬性值在實際應用時大多數是表達式,比如用來接收model的對象:

masking:text="${phone}"

如何將${phone}解析爲phone具體的值呢?請看下面代碼:

private Object getExpressionValue(ITemplateContext iTemplateContext, String expressionString) {
    final IEngineConfiguration configuration = iTemplateContext.getConfiguration();
    final IStandardExpressionParser parser = StandardExpressions.getExpressionParser(configuration);
    // 解析expression
    final IStandardExpression expression = parser.parseExpression(iTemplateContext, expressionString);
    // 獲取expression的執行結果
    return expression.execute(iTemplateContext);
}

定義方言類

編寫好之定義標籤的處理器之後,別忘了定義一個方言類。在方言類中,我們需要給出方言的名稱,前綴,處理優先級和涉及到的一系列自定義標籤處理器。代碼如下所示:

public class DataMaskingDialect extends AbstractProcessorDialect {
    private static final String PREFIX = "masking";
    public DataMaskingDialect() {
        // 方言名稱,前綴,處理優先級
        super("Data Masking Dialect", "masking", StandardDialect.PROCESSOR_PRECEDENCE);
    }

    @Override
    public Set<IProcessor> getProcessors(String s) {
        // 把所有的自定義tag處理器加入處理器集,這個例子中我們只有這一個自定義處理器
        final Set<IProcessor> processorSet = new HashSet<>();
        DataMaskingDialectTagProcessor dataMaskingDialectTagProcessor = new DataMaskingDialectTagProcessor(PREFIX);
        processorSet.add(dataMaskingDialectTagProcessor);
        return processorSet;
    }
}

在SpringBoot中加載自定義方言

即將大功告成了。在SpringBoot中使用自定義方言之前,務必要加載自定義的方言類,否則masking:text標籤將不會被解析。

@Configuration
public class DialectConfig() {
    @Bean
    public DataMaskingDialect dataMaskingDialect() {
        return new DataMaskingDialect();
    }
}

本博客爲作者原創,歡迎大家參與討論和批評指正。如需轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章