当前位置: 首页 > 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;//     }// }
}

相关文章:

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

Java 模板变量替换——字符串替换器&#xff08;思路Mybatis的GenericTokenParser&#xff09; 思路字符串替换器 思路 模板变量替换无非是寻找出字符串&#xff08;模板&#xff09;中的特殊标记&#xff0c;用对应的变量进行字符串替换。 提到变量替换&#xff0c;大家第一能…...

跨界融合:人工智能与区块链如何重新定义数据安全?

引言&#xff1a;数据安全的挑战与现状 在信息化驱动的数字化时代&#xff0c;数据已成为企业和个人最重要的资产之一。然而&#xff0c;随着网络技术的逐步优化和数据量的爆发式增长&#xff0c;数据安全问题也愈变突出。 数据安全现状&#xff1a;– 数据泄露驱动相关事件驱…...

android 自定义SwitchCompat,Radiobutton,SeekBar样式

纯代码的笔记记录。 自定义SwitchCompat按钮的样式 先自定义中间的圆球switch_thumb_bg.xml <?xml version"1.0" encoding"utf-8"?> <shape xmlns:android"http://schemas.android.com/apk/res/android"android:shape"oval&q…...

计算机网络的定义与发展历程

计算机网络的定义 计算机网络是指通过通信设备和传输介质将分布在不同地点的计算机及其相关设备&#xff08;如打印机、服务器等&#xff09;连接起来&#xff0c;按照一定的通信协议进行数据交换与资源共享的系统。计算机网络的基本功能包括&#xff1a;信息的传输、资源共享…...

对比学习 (Contrastive Learning) 算法详解与PyTorch实现

对比学习 (Contrastive Learning) 算法详解与PyTorch实现 目录 对比学习 (Contrastive Learning) 算法详解与PyTorch实现1. 对比学习 (Contrastive Learning) 算法概述1.1 自监督学习1.2 对比学习的优势2. 对比学习的核心技术2.1 正样本对与负样本对2.2 对比损失函数2.3 数据增…...

DBeaver执行本地的sql语句文件避免直接在客户端运行卡顿

直接在客户端运行 SQL 语句和通过加载本地文件执行 SQL 语句可能会出现不同的性能表现&#xff0c;原因可能包括以下几点&#xff1a; 客户端资源使用&#xff1a; 当你在客户端界面直接输入和执行 SQL 语句时&#xff0c;客户端可能会消耗资源来维护用户界面、语法高亮、自动完…...

C++ 的 pair 和 tuple

1 std::pair 1.1 C 98 的 std::pair 1.1.1 std::pair 的构造 ​ C 的二元组 std::pair<> 在 C 98 标准中就存在了&#xff0c;其定义如下&#xff1a; template<class T1, class T2> struct pair;std::pair<> 是个类模板&#xff0c;它有两个成员&#x…...

Zookeeper 集群安装

Zookeeper 集群 主机 IP SoftWare Port OS Myidnode1 192.168.230.128 apache-zookeeper-3.7.1 2181 Centos 7 1 node2 192.168.230.129 apache-zookeeper-3.7.1...

git merge与rebase区别以及实际应用

在 Git 中&#xff0c;merge 和 rebase 是两种将分支的更改合并到一起的常用方法。虽然它们都可以实现类似的目标&#xff0c;但它们的工作方式和效果有所不同。 1. Git Merge 定义&#xff1a;git merge 是将两个分支的历史合并在一起的一种操作。当你执行 git merge 时&…...

kvm虚拟机出现应用程序无法正常启动报0xc0000142错误

场景&#xff1a;我的是window10虚拟机&#xff0c;在运行我的软件的时候&#xff0c;出现0xc0000142错误&#xff0c;原因可能是cpu型号问题&#xff0c;某些虚拟cpu可能没有特定的指令&#xff0c;只需要修改虚拟机配置文件以下参数即可...

Redis 安装与 Spring Boot 集成指南

安装 Redis 和将其与 Spring Boot 应用集成是构建高效缓存解决方案的常见步骤。以下是详细的指南&#xff0c;帮助你在本地环境中安装 Redis&#xff0c;并在 Spring Boot 项目中配置和使用它。 1. 安装 Redis Windows 环境 Redis 官方并不直接支持 Windows&#xff0c;但你…...

Flink集成TDEngine来批处理或流式读取数据进行流批一体化计算(Flink SQL)拿来即用的案例

Flink 以其流批一体化的编程模型而备受青睐。它支持高吞吐、低延迟的实时流计算,同时在批处理方面也表现出色。Flink 提供了丰富的 API,如 DataStream API 和 DataSet API,方便开发者进行数据处理操作,包括转换、聚合、连接等,使得开发者能够轻松构建复杂的数据处理逻辑。…...

【STM32】利用SysTick定时器定时1s

1.SysTick简单介绍 SysTick定时器是一个24位的倒计数定时器&#xff0c;当计数到0时&#xff0c;将从RELOAD寄存器中自动重装载定时初值&#xff0c;开始新一轮计数。 SysTick定时器用于在每隔一定的时间产生一个中断&#xff0c;即使在系统睡眠模式下也能工作。 关于SysTic…...

Python中的format格式化、填充与对齐、数字格式化方式

文章目录 一、format语法二、format格式化的用法2.1、按照先后顺序替换{}2.2、按照索引进行匹配替换{0}2.3、按关键字索引进行匹配替换2.4、通过列表索引格式化字符串2.5、使用元组2.6、通过字典设置格式化字符串2.7、混合使用 三、字符串填充与对齐3.1、左对齐及填充3.2、右对…...

winform第三方界面开源库AntdUI的使用教程保姆级环境设置篇

1. AntdUI 1.1. 导入项目 1.1.1. 首先新建一个空白的基于.net的Winfrom项目1.1.2. 复制AntdUI中src目录到我们的解决方案下面1.1.3. 解决方案下添加现有项目1.1.4. 添加项目引用 1.2. 编写代码 1.2.1. 改写Form1类&#xff0c;让其继承自public partial class Form1 : AntdUI.W…...

如何使用Yarn Workspaces实现Monorepo模式在一个仓库中管理多个项目

Yarn Workspaces是Yarn提供的一种依赖管理机制&#xff0c;它支持在单个代码仓库中管理多个包的依赖。这种机制非常适合需要多个相互依赖的包的项目&#xff0c;能够减少重复依赖&#xff0c;加快依赖安装速度&#xff0c;并简化依赖管理。下面将详细介绍如何使用Yarn Workspac…...

SpringCloud系列教程:微服务的未来(十一)服务注册、服务发现、OpenFeign快速入门

本篇博客将通过实例演示如何在 Spring Cloud 中使用 Nacos 实现服务注册与发现&#xff0c;并使用 OpenFeign 进行服务间调用。你将学到如何搭建一个完整的微服务通信框架&#xff0c;帮助你快速开发可扩展、高效的分布式系统。 目录 前言 服务注册和发现 服务注册 ​编辑 …...

物联网:七天构建一个闭环的物联网DEMO

我计划用七天的时间&#xff0c; 基于开源物联网平台&#xff0c; 打造一款物联网案例的闭环。 为了增加感观体验&#xff0c;欢迎大家与我保持亲密的沟通。 我们来看一段代码&#xff1a; Slf4j Component public class MqttSendManager {Resourceprivate MqttSendHandler m…...

景联文科技提供高质量多模态数据处理服务,驱动AI新时代

在当今快速发展的AI时代&#xff0c;多模态数据标注成为推动人工智能技术进步的关键环节。景联文科技作为行业领先的AI数据服务提供商&#xff0c;专注于为客户提供高质量、高精度的多模态数据标注服务&#xff0c;涵盖图像、语音、文本、视频及3D点云等多种类型的数据。通过专…...

c#13新特性

C# 13 即 .NET 9 按照计划会在2024年11月发布&#xff0c;目前一些新特性已经定型&#xff0c;让我们来预览一个比较大型比较重要的新特性。 正文 扩展类型 Extension types 在5月份的微软 Build 大会中的 What’s new in C# 13 会议上&#xff0c;两位大佬花了很长的篇幅来…...

脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)

一、数据处理与分析实战 &#xff08;一&#xff09;实时滤波与参数调整 基础滤波操作 60Hz 工频滤波&#xff1a;勾选界面右侧 “60Hz” 复选框&#xff0c;可有效抑制电网干扰&#xff08;适用于北美地区&#xff0c;欧洲用户可调整为 50Hz&#xff09;。 平滑处理&…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

从WWDC看苹果产品发展的规律

WWDC 是苹果公司一年一度面向全球开发者的盛会&#xff0c;其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具&#xff0c;对过去十年 WWDC 主题演讲内容进行了系统化分析&#xff0c;形成了这份…...

边缘计算医疗风险自查APP开发方案

核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

【HTTP三个基础问题】

面试官您好&#xff01;HTTP是超文本传输协议&#xff0c;是互联网上客户端和服务器之间传输超文本数据&#xff08;比如文字、图片、音频、视频等&#xff09;的核心协议&#xff0c;当前互联网应用最广泛的版本是HTTP1.1&#xff0c;它基于经典的C/S模型&#xff0c;也就是客…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...