【Mybatis】使用 org.apache.ibatis.parsing.PropertyParser 造一个自己的模板引擎

本文介绍了在后端编程中如何解决文本变量解析与替换的问题。作者探讨了使用正则表达式、模板引擎和自建解决方案的优缺点,并提出MyBatis源码中存在现成的解决方案。通过一个测试用例展示了如何利用MyBatis的MyPropertyParser进行变量替换,该解析器支持默认值处理,且经过大量用户验证,降低了出错风险。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在后端编程中,我们经常要做对目标文本进行变量的解析与替换工作。
这时候就会面临纠结,

  1. 正则表达式去替换,担心性能问题;
  2. 模板引擎去解析,又觉得是在用高射炮打蚊子,还要引入了第三方依赖;
  3. 想自己撸一个轮子,时间紧、任务重,又担心出 BUG。

那这下好了,Mybatis的源码里面就有这样现成的轮子,它的代码是经过千万用户的使用,总不用担心出 BUG 了吧?
PS:如果真被你用出 BUG 了,转手就去Mybatis搞一波issue + pr也挺香的。

talk is cheap, show me the code.

softwareversion
open jdk1.8
MyBatis3.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;
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值