将模板填充数据并转换成字符串

方案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对应

 

posted @   java架构师1  阅读(375)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示