当前位置: 首页 > news >正文

Java 模板变量替换——字符串替换器(思路Mybatis的GenericTokenParser)

Java 模板变量替换——字符串替换器(思路Mybatis的GenericTokenParser)

  • 思路
  • 字符串替换器

思路

模板变量替换无非是寻找出字符串(模板)中的特殊标记,用对应的变量进行字符串替换。
提到变量替换,大家第一能联想到的可能是Mybatis的动态SQL语句的解析,又或者是mybatis.xml配置文件中用于解析"${username:root}"的默认值。
在Mybatis的源码中,这些功能则是通过GenericTokenParser类来实现的,通过查看源码可以很明显的知道GenericTokenParser只是查找指定的占位符,而具体的解析行为会根据其持有的TokenHandler实现的不同而有所不同,这里是采用了策略模式。
那么接下来我们就使用Mybatis的思路,试着写一个字符串替换器吧

字符串替换器

GenericTokenParser

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()) {return "";}// search open tokenint start = text.indexOf(openToken, 0);if (start == -1) {return text;}char[] src = text.toCharArray();int offset = 0;final StringBuilder builder = new StringBuilder();StringBuilder expression = null;while (start > -1) {if (start > 0 && src[start - 1] == '\\') {// this open token is escaped. remove the backslash and continue.builder.append(src, offset, start - offset - 1).append(openToken);offset = start + openToken.length();} else {// found open token. let's search close token.if (expression == null) {expression = new StringBuilder();} else {expression.setLength(0);}builder.append(src, offset, start - offset);offset = start + openToken.length();int end = text.indexOf(closeToken, offset);while (end > -1) {if (end > offset && src[end - 1] == '\\') {// this close token is escaped. remove the backslash and continue.expression.append(src, offset, end - offset - 1).append(closeToken);offset = end + closeToken.length();end = text.indexOf(closeToken, offset);} else {expression.append(src, offset, end - offset);offset = end + closeToken.length();break;}}if (end == -1) {// close token was not found.builder.append(src, start, src.length - start);offset = src.length;} else {builder.append(handler.handleToken(expression.toString()));offset = end + closeToken.length();}}start = text.indexOf(openToken, offset);}if (offset < src.length) {builder.append(src, offset, src.length - offset);}return builder.toString();}
}

TokenHandler

public interface TokenHandler {String handleToken(String content);
}

以上代码是Mybatis的源码,可以直接抄,接下来编写核心的逻辑:
这里的数据使用Map<String,Object>来存储,key是变量名称,value是变量值,value的类型下列代码仅支持了String和List,对应的可以实现直接变量替换和循环变量替换,有其他需要可以在此基础上新增功能,而List的泛型仅支持Map<String,String>,即不支持多层嵌套。
在这个例子中,我使用${变量名}表示普通变量,<变量名></变量名>表示循环变量,标签内的内容将被循环展示

public class ReportGenerator {public static void main(String[] args) {String reportTemplate = "这是一份贵司专属总结报告,请查收!\n" +"您的订单编号:${firstShowId},产品名称:${firstProductName},于${firstOnlineDate}正式生效,今天是贵司正式启用电子签名的第90天,截至今天,您的订单使用情况如下:#{${a}/${b}}\n" +"<orders>${orders.productName},累计消耗${orders.displayConsumeAmount}${orders.units}</orders>" +"如对使用情况和数据报告有任何疑问,可随时联系你的客户成功经理。\n" +"<contracts>合同编号${contracts.contractId},合同负责人:${contracts.contractManager},${name}</contracts>";Map<String, Object> data = new HashMap<>();data.put("firstShowId", "123456789");data.put("firstProductName", "测试产品");data.put("firstOnlineDate", "2023-01-01");data.put("productName", "测试产品");data.put("name", "张三");data.put("a", "100");data.put("b", "10");data.put("c", "10");List<Map<String, String>> orders = new ArrayList<>();Map<String, String> param1 = new HashMap<>();param1.put("productName", "测试产品1");param1.put("displayConsumeAmount", "100");param1.put("units", "元");orders.add(param1);Map<String, String> param2 = new HashMap<>();param2.put("productName", "测试产品2");param2.put("displayConsumeAmount", "200");param2.put("units", "元");orders.add(param2);data.put("orders", orders);List<Map<String, String>> contracts = new ArrayList<>();Map<String, String> param3 = new HashMap<>();param1.put("contractId", "12312543243213");param1.put("contractManager", "杜甫");contracts.add(param1);Map<String, String> param4 = new HashMap<>();param2.put("contractId", "1234353453");param2.put("contractManager", "李白");contracts.add(param2);data.put("contracts", contracts);String handler = handler(data, reportTemplate);// 打印报告System.out.println(handler);}public static String handler(Map<String, Object> data, String templateContent) {// 循环变量,仅支持一层嵌套Map<String, List<Map<String, String>>> loopVariables = new HashMap<>();// 基本参数Map<String, String> basicVariables = new HashMap<>();// 分类:循环变量,基本参数,计算参数for (Map.Entry<String, Object> entry : data.entrySet()) {String key = entry.getKey();Object value = entry.getValue();if (value instanceof List) {loopVariables.put(key, (List<Map<String, String>>) value);} else if (value instanceof String) {basicVariables.put(key, (String) value);}}// 处理循环变量for (Map.Entry<String, List<Map<String, String>>> entry : loopVariables.entrySet()) {String key = entry.getKey();List<Map<String, String>> value = entry.getValue();ForEachTokenParser forEachTokenParser = new ForEachTokenParser(key, value, "\n");GenericTokenParser genericTokenParser = new GenericTokenParser("<" + key + ">", "</" + key + ">", forEachTokenParser);templateContent = genericTokenParser.parse(templateContent);}// 处理基本参数VariableTokenParser variableTokenParser = new VariableTokenParser(basicVariables);GenericTokenParser genericTokenParser = new GenericTokenParser("${", "}", variableTokenParser);templateContent = genericTokenParser.parse(templateContent);// // 计算参数// EquationTokenParser equationTokenParser = new EquationTokenParser();// GenericTokenParser equationParser = new GenericTokenParser("#{", "}", equationTokenParser);// templateContent = equationParser.parse(templateContent);return templateContent;}/*** 循环变量参数*/static class ForEachTokenParser implements TokenHandler {private final String variableName;private final List<Map<String, String>> variables;private final String separator;public ForEachTokenParser(String variableName, List<Map<String, String>> variable, String separator) {this.variableName = variableName;this.variables= variable;this.separator = separator;}@Overridepublic String handleToken(String content) {if (variables == null || variables.isEmpty()) {throw new RuntimeException("变量不存在:" + content);}StringBuilder builder = new StringBuilder();for (Map<String, String> variable : variables) {VariableTokenParser variableTokenParser = new VariableTokenParser(variable);GenericTokenParser genericTokenParser = new GenericTokenParser("${" + variableName + ".", "}", variableTokenParser);builder.append(genericTokenParser.parse(content)).append(separator);}return builder.toString();}}/*** 基础变量参数*/static class VariableTokenParser implements TokenHandler {private final Map<String, String> variables;public VariableTokenParser(Map<String, String> variable) {this.variables= variable;}@Overridepublic String handleToken(String content) {String value = variables.get(content);if (value == null) {throw new RuntimeException("变量不存在:" + content);}return value;}}/*** 计算参数* 目前仅支持除法运算:#{100/10}*/// static class EquationTokenParser implements TokenHandler {////     @Override//     public String handleToken(String content) {//         return String.valueOf(evaluateExpression(content));//     }////     private static double evaluateExpression(String expression) {//         String[] parts = expression.split("/");//         if (parts.length == 2) {//             BigDecimal numerator = new BigDecimal(parts[0].trim());//             BigDecimal denominator = new BigDecimal(parts[1].trim());//             return numerator.divide(denominator, 2, RoundingMode.HALF_UP).doubleValue();//         }//         return 0;//     }// }
}

http://www.mrgr.cn/news/83217.html

相关文章:

  • 中文博客排版指南
  • 单片机-定时器中断
  • spring boot学习第二十三篇:Spring Boot集成RocketMQ
  • UE播放声音
  • 基于高斯混合模型的数据分析及其延伸应用(具体代码分析)
  • C# 事件
  • 面试加分项:Android Framework PMS 全面概述和知识要点
  • 用BaoStock判断一只股票昨天是否涨停~~
  • 《自动驾驶与机器人中的SLAM技术》ch2:基础数学知识
  • java-方法详解
  • Springboot Bean创建流程、三种Bean注入方式(构造器注入、字段注入、setter注入)、循坏依赖问题
  • 【机器视觉】OpenCV 图像金字塔(高斯、拉普拉斯)和直方图
  • 统一门户单点登入(C#-OOS机制)
  • 04、Redis深入数据结构
  • 使用PVE快速创建虚拟机集群并搭建docker环境
  • https原理
  • 华为C语言编程规范总结
  • WinCC flexible SMART V4 SP2软件安装事项
  • C++中的表达式
  • 高性能网络模式:Reactor 和 Proactor
  • Linux标准IOday3
  • yum换源
  • Spring——自动装配
  • 【python基础——异常BUG】
  • redis:安装部署、升级以及失败回退
  • 【算法】八大排序算法