在后端编程中,我们经常要做对目标文本进行变量的解析与替换工作。
这时候就会面临纠结,
- 用
正则表达式
去替换,担心性能问题;- 用
模板引擎
去解析,又觉得是在用高射炮打蚊子,还要引入了第三方依赖;- 想自己撸一个轮子,时间紧、任务重,又担心出 BUG。
那这下好了,
Mybatis
的源码里面就有这样现成的轮子,它的代码是经过千万用户的使用,总不用担心出 BUG 了吧?
PS:如果真被你用出 BUG 了,转手就去Mybatis
搞一波issue + pr
也挺香的。
talk is cheap, show me the code.
software | version |
---|---|
open jdk | 1.8 |
MyBatis | 3.5.5 |
测试用例
package com.itplh.parse;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Properties;
public class MyPropertyParserTest {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put("hostName", "my-property-parser-test");
properties.put("ip", "123.123.123.1");
String ymd = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now());
properties.put("alarmTime", ymd);
properties.put("currentTime", ymd);
String text1 = new StringBuilder()
.append("【监控报警】\n")
.append("报警机器:${hostName} | ${ip}\n")
.append("报警时间:${alarmTime}\n")
.append("通知时间:${currentTime}\n")
.toString();
System.out.println(text1);
System.out.println(MyPropertyParser.parse(text1, properties));
String text2 = new StringBuilder()
.append("【监控报警】\n")
.append("配置名称:{{config:默认配置}}\n") // config设置了默认值
.append("报警机器:{{hostName}} | {{ip:0.0.0.0}}\n") // ip设置了默认值
.append("报警时间:{{alarmTime}}\n")
.append("通知时间:{{currentTime}}\n")
.toString();
// 启用默认值 定义包裹变量的符号为{{}}
properties.put(VariableTokenHandler.ENABLE_DEFAULT_VALUE_KEY, "true");
System.out.println(text2);
System.out.println(MyPropertyParser.parse(text2, "{{", "}}", properties));
}
}
控制台输出
【监控报警】
报警机器:${hostName} | ${ip}
报警时间:${alarmTime}
通知时间:${currentTime}
【监控报警】
报警机器:my-property-parser-test | 123.123.123.1
报警时间:2022-06-12 15:21:35
通知时间:2022-06-12 15:21:35
【监控报警】
配置名称:{{config:默认配置}}
报警机器:{{hostName}} | {{ip:0.0.0.0}}
报警时间:{{alarmTime}}
通知时间:{{currentTime}}
【监控报警】
配置名称:默认配置
报警机器:my-property-parser-test | 123.123.123.1
报警时间:2022-06-12 15:21:35
通知时间:2022-06-12 15:21:35
源码
MyPropertyParser
package com.itplh.parse;
import java.util.Properties;
public class MyPropertyParser {
public static String parse(String text, Properties variables) {
return parse(text, "${", "}", variables);
}
public static String parse(String text, String openToken, String closeToken, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
GenericTokenParser parser = new GenericTokenParser(openToken, closeToken, handler);
return parser.parse(text);
}
}
GenericTokenParser
package com.itplh.parse;
public class GenericTokenParser {
private final String openToken;
private final String closeToken;
private final TokenHandler handler;
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
public String parse(String text) {
if (text != null && !text.isEmpty()) {
int start = text.indexOf(this.openToken);
if (start == -1) {
return text;
} else {
char[] src = text.toCharArray();
int offset = 0;
StringBuilder builder = new StringBuilder();
for (StringBuilder expression = null; start > -1; start = text.indexOf(this.openToken, offset)) {
if (start > 0 && src[start - 1] == '\\') {
builder.append(src, offset, start - offset - 1).append(this.openToken);
offset = start + this.openToken.length();
} else {
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + this.openToken.length();
int end;
for (end = text.indexOf(this.closeToken, offset); end > -1; end = text.indexOf(this.closeToken, offset)) {
if (end <= offset || src[end - 1] != '\\') {
expression.append(src, offset, end - offset);
break;
}
expression.append(src, offset, end - offset - 1).append(this.closeToken);
offset = end + this.closeToken.length();
}
if (end == -1) {
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(this.handler.handleToken(expression.toString(), this.openToken, this.closeToken));
offset = end + this.closeToken.length();
}
}
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
} else {
return "";
}
}
}
TokenHandler
package com.itplh.parse;
public interface TokenHandler {
String handleToken(String content, String openToken, String closeToken);
}
VariableTokenHandler
package com.itplh.parse;
import java.util.Properties;
public class VariableTokenHandler implements TokenHandler {
public static final String ENABLE_DEFAULT_VALUE_KEY = "enable-default-value";
public static final String DEFAULT_VALUE_SEPARATOR_KEY = "default-value-separator";
private final Properties variables;
private final boolean enableDefaultValue;
private final String defaultValueSeparator;
public VariableTokenHandler(Properties variables) {
this.variables = variables;
this.enableDefaultValue = Boolean.parseBoolean(this.getPropertyValue(ENABLE_DEFAULT_VALUE_KEY, "false"));
this.defaultValueSeparator = this.getPropertyValue(DEFAULT_VALUE_SEPARATOR_KEY, ":");
}
private String getPropertyValue(String key, String defaultValue) {
return this.variables == null ? defaultValue : this.variables.getProperty(key, defaultValue);
}
@Override
public String handleToken(String content, String openToken, String closeToken) {
if (this.variables != null) {
String key = content;
if (this.enableDefaultValue) {
int separatorIndex = content.indexOf(this.defaultValueSeparator);
String defaultValue = null;
if (separatorIndex >= 0) {
key = content.substring(0, separatorIndex);
defaultValue = content.substring(separatorIndex + this.defaultValueSeparator.length());
}
if (defaultValue != null) {
return this.variables.getProperty(key, defaultValue);
}
}
if (this.variables.containsKey(key)) {
return this.variables.getProperty(key);
}
}
return openToken + content + closeToken;
}
}