将模板填充数据并转换成字符串
方案1:springboot集成freemarker
https://www.cnblogs.com/lipg/p/13761588.html
方案2:参照Mybatis 手写功能
原始请求:拼接XML字符串发送请求
解决方案设计:像mybatis一样写XML文件 然后#{}的方式注入值 唯一区别就是方法调用的时候返回一个拼接的字符串而不是执行SQL
解决思路:
1. 定义自定义注解@ErpMapper 像 @Mapper 一样
2. 设计:把注解打在接口上 然后生成代理对象 放入Spring 中 ,实现过于繁琐 自己生成的代理对象很难注入到Spring中
3. 更改设计:把注解打在Class上,然后用@Service注入 再用AOP形成切面去代理方法(方法返回值约束为String)
4. 代理方法内部实现逻辑
1. 实现InitializingBean 接口 并调用this.initXmlMap()方法,该方法在扫描path路径下的文件,并解析XML文件添加进classMap,最后再把classMap添加进缓存xmlData
2. 解析并添加map ,map内存储的ErpMapperXmlAnalysisDTO实际上主要存储一个NodeList
3. 上面两布在做扫描xml文件并添加节点的操作 ,然后在AOP中根据类名 方法名 拿出对应的NodeList再处理就好了
代码:
import java.lang.annotation.*; @Documented @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD}) public @interface ErpMapper { // Interface ErpMapper }
package com.ruijie.purchase.erp; import com.alibaba.nacos.common.utils.StringUtils; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Component; import org.w3c.dom.*; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.File; import java.io.IOException; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * @menu ErpMapperAspect处理 * @BelongsProject: minor-purchase2 * @BelongsPackage: com.ruijie.purchase.erp * @Author: rjxiaozhenjie@ruijie.com.cn * @CreateTime: 2023-04-13 19:07 * @Version: 1.0 */ @Aspect @Component @Slf4j public class ErpMapperAspect implements InitializingBean { /** * 弱引用 缓存数据 key类名 value = key1方法名 value1 需要拼接和解析字符串的node对象 */ private WeakReference< Map<String, Map<String, ErpMapperXmlAnalysisDTO>>> xmlData = new WeakReference<>(null); private String path = "erpMapper"; private final Object lock = new Object(); /** * * 对缓存赋值 * * @author rjxiaozhenjie@ruijie.com.cn * @date 2023/4/14 15:34:20 */ @Override public void afterPropertiesSet() throws Exception { this.initXmlMap(); } /** * * 初始化map * * @author rjxiaozhenjie@ruijie.com.cn * @date 2023/4/14 17:09:32 * @return java.util.Map<java.lang.String, java.util.Map < java.lang.String, com.ruijie.purchase.erp.ErpMapperXmlAnalysisDTO>> */ @NotNull private Map<String, Map<String, ErpMapperXmlAnalysisDTO>> initXmlMap() throws ParserConfigurationException, SAXException, IOException { //1.获取当前项目路径 扫描erpMapper文件下的全部XML File f = new File(Objects.requireNonNull(ErpMapperAspect.class.getResource("/")).getPath()); //key:类名 value:map , map 存后面的 methodMap Map<String, Map<String, ErpMapperXmlAnalysisDTO>> classMap = new HashMap<>(8); //创建解析XML工厂 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); File[] files = f.listFiles(); for (File mapper : files) { //如果找到了这个包 if(path.equals(mapper.getName()) && mapper.isDirectory()){ //扫描全部XML文件 File[] files1 = mapper.listFiles(); for (File file : files1) { //如果是xml文件 String name = file.getName(); if (name.endsWith(".xml")) { //DTO 解析并添加MAP parseXmlDataAndPut(classMap, builder, file); } } } } //对缓存赋值 xmlData = new WeakReference<>(classMap); return classMap; } /** * * 解析数据并添加进map * * @author rjxiaozhenjie@ruijie.com.cn * @date 2023/4/14 16:56:11 * @param classMap 存储解析结果 * @param builder xml文件解析工厂 * @param file 需要解析的xml文件 */ private void parseXmlDataAndPut(Map<String, Map<String, ErpMapperXmlAnalysisDTO>> classMap, DocumentBuilder builder, File file) throws SAXException, IOException { //类名 String className = file.getName().replace(".xml", ""); Document document = builder.parse(file); //根 一般为erpMapper Element root = document.getDocumentElement(); //获取全部子节点 一般都为 <select> 标签 此处不校验 让XML的 dtd规则文件去校验 NodeList childNodes = root.getChildNodes(); //key 方法名 value 解析XML对象 内部有id resultType 以及需要拼接成String的节点 Map<String, ErpMapperXmlAnalysisDTO> methodMap = new HashMap<>(8); for (int i = 0; i < childNodes.getLength(); i++) { Node item = childNodes.item(i); //每一个select的属性 如 id resultType NamedNodeMap attributes = item.getAttributes(); if(attributes != null){ Node id = attributes.getNamedItem("id"); Node resultType = attributes.getNamedItem("resultType"); ErpMapperXmlAnalysisDTO erpMapperXmlAnalysisDTO = new ErpMapperXmlAnalysisDTO(); //id 必填 因为此处ID对应方法名称 if(id == null){ throw new RuntimeException("ErpMapper XML文件解析异常 select 标签内 id 必填!"); } String idNodeValue = id.getNodeValue(); erpMapperXmlAnalysisDTO.setId(idNodeValue); //可以不填 也可以填 if(resultType != null){ erpMapperXmlAnalysisDTO.setResultType(resultType.getNodeValue()); } //需要拼接字符串的全部节点 NodeList nodeList = item.getChildNodes(); //保存这些节点 等代理的时候拿出来解析并拼接字符串 erpMapperXmlAnalysisDTO.setNodeList(nodeList); methodMap.put(idNodeValue,erpMapperXmlAnalysisDTO); } } classMap.put(className,methodMap); } /** * * 切点 被注解ErpMapper标记的方法 * * @author rjxiaozhenjie@ruijie.com.cn * @date 2023/4/14 17:02:56 */ @Pointcut("@annotation(com.ruijie.purchase.erp.ErpMapper)") public void erpMapper() { } @Around("erpMapper()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { Class<?> aClass = joinPoint.getTarget().getClass(); // 类名 String className = aClass.getSimpleName(); //方法名 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String methodName = signature.getName(); //从缓存map中拿map 如果map为空调用初始化方法 Map<String, Map<String, ErpMapperXmlAnalysisDTO>> classMap = xmlData.get(); //双检锁 if(classMap == null){ synchronized (lock){ //从缓存map中拿map 如果map为空调用初始化方法 classMap = xmlData.get(); //去初始化 if(classMap == null){ classMap = this.initXmlMap(); } } } Map<String, ErpMapperXmlAnalysisDTO> methodMap = classMap.get(className); ErpMapperXmlAnalysisDTO erpMapperXmlAnalysisDTO = methodMap.get(methodName); if(erpMapperXmlAnalysisDTO == null){ //如果没有找到对应的Mapper XML文件 ,报错 throw new RuntimeException(String.format("类名:%s 方法名:%s 未找到对应的 ErpMapper",className,methodName)); } //获取方法入参 Object[] args = joinPoint.getArgs(); Class<?>[] parameterTypes = signature.getParameterTypes(); //字符串结果拼接 StringBuilder sb = new StringBuilder(256); //此处只取一个入参 其他不考虑 否则需要增加 多参数注解@param if (parameterTypes[0] != null) { //类型正确 遍历全部节点 并且拼接字符串 替换内部#{}标记的数据 //解析数据并且拼接字符串 this.dfs(erpMapperXmlAnalysisDTO.getNodeList(),sb,parameterTypes[0],args[0]); } //返回结果 return sb.toString(); } /** * * 深度优先遍历 拼接标签 并解析 * * @author rjxiaozhenjie@ruijie.com.cn * @date 2023/4/14 17:46:33 * @param nodeList * @param sb 存储结果 * @param parameterType 入参class * @param arg 入参具体对象 */ private void dfs(NodeList nodeList, StringBuilder sb, Class<?> parameterType, Object arg) throws NoSuchFieldException, IllegalAccessException { //出口 if(nodeList == null || nodeList.getLength() == 0){ return; } for (int i = 0; i < nodeList.getLength(); i++) { Node item = nodeList.item(i); String nodeValue = item.getNodeValue(); //如果是单标签 if(nodeValue == null && !item.hasChildNodes()){ sb.append("<").append(item.getNodeName()).append("/>"); continue; } if("#text".equals(item.getNodeName()) && StringUtils.isEmpty(item.getNodeValue().trim())){ continue; } if(!"#text".equals(item.getNodeName())){ sb.append("<").append(item.getNodeName()).append(">"); } if (!item.hasChildNodes()) { //解析数据 nodeValue = item.getNodeValue(); int i1 = nodeValue.indexOf("#{"); while(i1 != -1){ int i2 = nodeValue.indexOf("}"); //拼接 #{ 前面部分数据 sb.append(nodeValue, 0, i1); String filedName = nodeValue.substring(i1 + 2, i2); //解析 #{}内数据 Field declaredField = parameterType.getDeclaredField(filedName); //开启权限 declaredField.setAccessible(true); //获取值 Object o = declaredField.get(arg); sb.append(o); if(nodeValue.length() > i2 +1){ //剩下的部分重新赋值 nodeValue = nodeValue.substring(i2+1); }else{ nodeValue = ""; } i1 = nodeValue.indexOf("#{"); } sb.append(nodeValue); }else{ dfs(item.getChildNodes(),sb,parameterType,arg); } if(!"#text".equals(item.getNodeName())) { sb.append("</").append(item.getNodeName()).append(">"); } } } }
package com.ruijie.purchase.erp; import lombok.Data; import org.w3c.dom.NodeList; /** * @menu ErpMapperXml解析DTO * @BelongsProject: minor-purchase2 * @BelongsPackage: com.ruijie.purchase.erp * @Author: rjxiaozhenjie@ruijie.com.cn * @CreateTime: 2023-04-14 15:57 * @Version: 1.0 */ @Data public class ErpMapperXmlAnalysisDTO { /** * id 一般用来存方法名 */ private String id; /** * 请求类型 */ private String resultType; /** * 需要拼接字符串的全部数据 */ private NodeList nodeList; }
package com.ruijie.purchase.erp; import com.ruijie.purchase.bean.PurchaseMinorDemand; import org.springframework.stereotype.Service; /** * ERP服务 * * @BelongsProject: minor-purchase2 * @BelongsPackage: com.ruijie.purchase.erp * @Author: rjxiaozhenjie@ruijie.com.cn * @CreateTime: 2023-04-13 18:45 * @Version: 1.0 */ @Service public class PurchaseMinorDemandErpMapper { @ErpMapper public String send(PurchaseMinorDemand purchaseMinorDemand){ return ""; } @ErpMapper public String send2(PurchaseMinorDemand purchaseMinorDemand){ return ""; } }
<?xml version="1.0" encoding="UTF-8"?> <!--<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">--> <erpMapper> <select id="send" resultType="java.lang.String"> <STD_IN> <HEAD> <SOURCE>MP</SOURCE> <SERVICENAME>CUX_ORACLE_UPDATE</SERVICENAME> <DEBUG>Y</DEBUG> <KEYWORD>QF_SYS_ROUTE_INTERFACE_PLATFORM</KEYWORD> <USERNAME/> <USERPWD/> <OPERATE>IF_MDS_MT_LOAD</OPERATE> </HEAD> <BODY> <LINE> <LINE_TYPE>LINE</LINE_TYPE> <S_STR_1>org</S_STR_1> <S_STR_2>org_MDS_MT</S_STR_2> <S_STR_3>#{itemNum}</S_STR_3> <S_STR_4>#{demandDate}</S_STR_4> <S_STR_5>#{demandQty}</S_STR_5> <S_STR_6>MP_MDS_MT</S_STR_6> <S_STR_7>#{id}</S_STR_7> <S_STR_8>零星采购项目</S_STR_8> <S_STR_9>#{demandType}</S_STR_9> </LINE> </BODY> </STD_IN> </select> <select id="send2"> <STD_IN2> <HEAD2> <SOURCE2>MP</SOURCE2> <SERVICENAME2>CUX_ORACLE_UPDATE</SERVICENAME2> <DEBUG2>Y</DEBUG2> <KEYWORD2>QF_SYS_ROUTE_INTERFACE_PLATFORM</KEYWORD2> <USERNAME2/> <USERPWD2/> <OPERATE2>IF_MDS_MT_LOAD</OPERATE2> </HEAD2> <BODY2> <foreach collection="list"> <LINE2> <LINE_TYPE2>LINE</LINE_TYPE2> <S_STR_12>org</S_STR_12> <S_STR_22>org_MDS_MT</S_STR_22> <S_STR_32>#{itemNum}</S_STR_32> <S_STR_42>#{demandDate}</S_STR_42> <S_STR_52>#{demandQty}</S_STR_52> <S_STR_62>MP_MDS_MT</S_STR_62> <S_STR_72>#{id}</S_STR_72> <S_STR_82>零星采购项目</S_STR_82> <S_STR_92>id:#{id} demandType:#{demandType} demandQty:#{demandQty} ==</S_STR_92> </LINE2> </foreach> </BODY2> </STD_IN2> </select> </erpMapper>
代码布局:erpMapper文件夹固定,与ErpMapperAspect内的path对应
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异