前言
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();
}
}
本博客爲作者原創,歡迎大家參與討論和批評指正。如需轉載請註明出處。