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

[复制链接]
发表于 2025-11-11 18:33:13 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
思绪

模板变量更换无非是探求出字符串(模板)中的特殊标志,用对应的变量举行字符串更换。
提到变量更换,各人第一能遐想到的大概是Mybatis的动态SQL语句的分析,又大概是mybatis.xml设置文件中用于分析"${username:root}"的默认值。
在Mybatis的源码中,这些功能则是通过GenericTokenParser类来实现的,通过查察源码可以很显着的知道GenericTokenParser只是查找指定的占位符,而具体的分析活动会根据其持有的TokenHandler实现的差别而有所差别,这里是采取了计谋模式。
那么接下来我们就使用Mybatis的思绪,试着写一个字符串更换器吧
字符串更换器

GenericTokenParser
  1. public class GenericTokenParser {
  2.   private final String openToken;
  3.   private final String closeToken;
  4.   private final TokenHandler handler;
  5.   public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
  6.     this.openToken = openToken;
  7.     this.closeToken = closeToken;
  8.     this.handler = handler;
  9.   }
  10.   public String parse(String text) {
  11.     if (text == null || text.isEmpty()) {
  12.       return "";
  13.     }
  14.     // search open token
  15.     int start = text.indexOf(openToken, 0);
  16.     if (start == -1) {
  17.       return text;
  18.     }
  19.     char[] src = text.toCharArray();
  20.     int offset = 0;
  21.     final StringBuilder builder = new StringBuilder();
  22.     StringBuilder expression = null;
  23.     while (start > -1) {
  24.       if (start > 0 && src[start - 1] == '\\') {
  25.         // this open token is escaped. remove the backslash and continue.
  26.         builder.append(src, offset, start - offset - 1).append(openToken);
  27.         offset = start + openToken.length();
  28.       } else {
  29.         // found open token. let's search close token.
  30.         if (expression == null) {
  31.           expression = new StringBuilder();
  32.         } else {
  33.           expression.setLength(0);
  34.         }
  35.         builder.append(src, offset, start - offset);
  36.         offset = start + openToken.length();
  37.         int end = text.indexOf(closeToken, offset);
  38.         while (end > -1) {
  39.           if (end > offset && src[end - 1] == '\\') {
  40.             // this close token is escaped. remove the backslash and continue.
  41.             expression.append(src, offset, end - offset - 1).append(closeToken);
  42.             offset = end + closeToken.length();
  43.             end = text.indexOf(closeToken, offset);
  44.           } else {
  45.             expression.append(src, offset, end - offset);
  46.             offset = end + closeToken.length();
  47.             break;
  48.           }
  49.         }
  50.         if (end == -1) {
  51.           // close token was not found.
  52.           builder.append(src, start, src.length - start);
  53.           offset = src.length;
  54.         } else {
  55.           builder.append(handler.handleToken(expression.toString()));
  56.           offset = end + closeToken.length();
  57.         }
  58.       }
  59.       start = text.indexOf(openToken, offset);
  60.     }
  61.     if (offset < src.length) {
  62.       builder.append(src, offset, src.length - offset);
  63.     }
  64.     return builder.toString();
  65.   }
  66. }
复制代码
TokenHandler
  1. public interface TokenHandler {
  2.   String handleToken(String content);
  3. }
复制代码
以上代码是Mybatis的源码,可以直接抄,接下来编写核心的逻辑:
这里的数据使用Map<String,Object>来存储,key是变量名称,value是变量值,value的范例下列代码仅支持了String和List,对应的可以实现直接变量更换和循环变量更换,有其他须要可以在此根本上新增功能,而List的泛型仅支持Map<String,String>,即不支持多层嵌套。
在这个例子中,我使用${变量名}表现平凡变量,<变量名></变量名>表现循环变量,标签内的内容将被循环展示
  1. public class ReportGenerator {
  2.     public static void main(String[] args) {
  3.         String reportTemplate = "这是一份贵司专属总结报告,请查收!\n" +
  4.                 "您的订单编号:${firstShowId},产品名称:${firstProductName},于${firstOnlineDate}正式生效,今天是贵司正式启用电子签名的第90天,截至今天,您的订单使用情况如下:#{${a}/${b}}\n" +
  5.                 "<orders>${orders.productName},累计消耗${orders.displayConsumeAmount}${orders.units}</orders>" +
  6.                 "如对使用情况和数据报告有任何疑问,可随时联系你的客户成功经理。\n" +
  7.                 "<contracts>合同编号${contracts.contractId},合同负责人:${contracts.contractManager},${name}</contracts>";
  8.         Map<String, Object> data = new HashMap<>();
  9.         data.put("firstShowId", "123456789");
  10.         data.put("firstProductName", "测试产品");
  11.         data.put("firstOnlineDate", "2023-01-01");
  12.         data.put("productName", "测试产品");
  13.         data.put("name", "张三");
  14.         data.put("a", "100");
  15.         data.put("b", "10");
  16.         data.put("c", "10");
  17.         List<Map<String, String>> orders = new ArrayList<>();
  18.         Map<String, String> param1 = new HashMap<>();
  19.         param1.put("productName", "测试产品1");
  20.         param1.put("displayConsumeAmount", "100");
  21.         param1.put("units", "元");
  22.         orders.add(param1);
  23.         Map<String, String> param2 = new HashMap<>();
  24.         param2.put("productName", "测试产品2");
  25.         param2.put("displayConsumeAmount", "200");
  26.         param2.put("units", "元");
  27.         orders.add(param2);
  28.         data.put("orders", orders);
  29.         List<Map<String, String>> contracts = new ArrayList<>();
  30.         Map<String, String> param3 = new HashMap<>();
  31.         param1.put("contractId", "12312543243213");
  32.         param1.put("contractManager", "杜甫");
  33.         contracts.add(param1);
  34.         Map<String, String> param4 = new HashMap<>();
  35.         param2.put("contractId", "1234353453");
  36.         param2.put("contractManager", "李白");
  37.         contracts.add(param2);
  38.         data.put("contracts", contracts);
  39.         String handler = handler(data, reportTemplate);
  40.         // 打印报告
  41.         System.out.println(handler);
  42.     }
  43.     public static String handler(Map<String, Object> data, String templateContent) {
  44.         // 循环变量,仅支持一层嵌套
  45.         Map<String, List<Map<String, String>>> loopVariables = new HashMap<>();
  46.         // 基本参数
  47.         Map<String, String> basicVariables = new HashMap<>();
  48.         // 分类:循环变量,基本参数,计算参数
  49.         for (Map.Entry<String, Object> entry : data.entrySet()) {
  50.             String key = entry.getKey();
  51.             Object value = entry.getValue();
  52.             if (value instanceof List) {
  53.                 loopVariables.put(key, (List<Map<String, String>>) value);
  54.             } else if (value instanceof String) {
  55.                 basicVariables.put(key, (String) value);
  56.             }
  57.         }
  58.         // 处理循环变量
  59.         for (Map.Entry<String, List<Map<String, String>>> entry : loopVariables.entrySet()) {
  60.             String key = entry.getKey();
  61.             List<Map<String, String>> value = entry.getValue();
  62.             ForEachTokenParser forEachTokenParser = new ForEachTokenParser(key, value, "\n");
  63.             GenericTokenParser genericTokenParser = new GenericTokenParser("<" + key + ">", "</" + key + ">", forEachTokenParser);
  64.             templateContent = genericTokenParser.parse(templateContent);
  65.         }
  66.         // 处理基本参数
  67.         VariableTokenParser variableTokenParser = new VariableTokenParser(basicVariables);
  68.         GenericTokenParser genericTokenParser = new GenericTokenParser("${", "}", variableTokenParser);
  69.         templateContent = genericTokenParser.parse(templateContent);
  70.         // // 计算参数
  71.         // EquationTokenParser equationTokenParser = new EquationTokenParser();
  72.         // GenericTokenParser equationParser = new GenericTokenParser("#{", "}", equationTokenParser);
  73.         // templateContent = equationParser.parse(templateContent);
  74.         return templateContent;
  75.     }
  76.     /**
  77.      * 循环变量参数
  78.      */
  79.     static class ForEachTokenParser implements TokenHandler {
  80.         private final String variableName;
  81.         private final List<Map<String, String>> variables;
  82.         private final String separator;
  83.         public ForEachTokenParser(String variableName, List<Map<String, String>> variable, String separator) {
  84.             this.variableName = variableName;
  85.             this.variables= variable;
  86.             this.separator = separator;
  87.         }
  88.         @Override
  89.         public String handleToken(String content) {
  90.             if (variables == null || variables.isEmpty()) {
  91.                 throw new RuntimeException("变量不存在:" + content);
  92.             }
  93.             StringBuilder builder = new StringBuilder();
  94.             for (Map<String, String> variable : variables) {
  95.                 VariableTokenParser variableTokenParser = new VariableTokenParser(variable);
  96.                 GenericTokenParser genericTokenParser = new GenericTokenParser("${" + variableName + ".", "}", variableTokenParser);
  97.                 builder.append(genericTokenParser.parse(content)).append(separator);
  98.             }
  99.             return builder.toString();
  100.         }
  101.     }
  102.     /**
  103.      * 基础变量参数
  104.      */
  105.     static class VariableTokenParser implements TokenHandler {
  106.         private final Map<String, String> variables;
  107.         public VariableTokenParser(Map<String, String> variable) {
  108.             this.variables= variable;
  109.         }
  110.         @Override
  111.         public String handleToken(String content) {
  112.             String value = variables.get(content);
  113.             if (value == null) {
  114.                 throw new RuntimeException("变量不存在:" + content);
  115.             }
  116.             return value;
  117.         }
  118.     }
  119.     /**
  120.      * 计算参数
  121.      * 目前仅支持除法运算:#{100/10}
  122.      */
  123.     // static class EquationTokenParser implements TokenHandler {
  124.     //
  125.     //     @Override
  126.     //     public String handleToken(String content) {
  127.     //         return String.valueOf(evaluateExpression(content));
  128.     //     }
  129.     //
  130.     //     private static double evaluateExpression(String expression) {
  131.     //         String[] parts = expression.split("/");
  132.     //         if (parts.length == 2) {
  133.     //             BigDecimal numerator = new BigDecimal(parts[0].trim());
  134.     //             BigDecimal denominator = new BigDecimal(parts[1].trim());
  135.     //             return numerator.divide(denominator, 2, RoundingMode.HALF_UP).doubleValue();
  136.     //         }
  137.     //         return 0;
  138.     //     }
  139.     // }
  140. }
复制代码
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表