欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Mybatis plus 3.1 xml 热更新

程序员文章站 2022-10-04 08:43:45
Mybatis plus 3.1 xml 热更新环境介绍功能说明代码环境介绍mybatis plus3.1 + springboot功能说明1.监控项目源码中的xml变化自动热更新代码import lombok.Data;import lombok.EqualsAndHashCode;import lombok.SneakyThrows;import lombok.experimental.Accessors;import lombok.extern.slf4j.Slf4j;impor...

Mybatis plus 3.1 xml 热更新

环境介绍

mybatis plus3.1 + springboot

功能说明

1.监控项目源码中的xml变化自动热更新

代码

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperRegistry;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.ResourceUtils;
import org.zyq.core.algorithm.MD5;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * zouyq 2020 07 15 
 */
@Slf4j
public class MybatisXmlHotRefresh implements Runnable {
    /**
     * 记录jar包存在的mapper
     */
    private static final Map<String, List<Resource>> jarMapper = new HashMap<>();
    private SqlSessionFactory sqlSessionFactory;
    private List<Resource> mapperLocations;
    private Configuration configuration;

    private Set<String> fileSet = new HashSet<>();

    private int delaySeconds = 1;

    private int sleepSeconds = 20;

    public MybatisXmlHotRefresh(SqlSessionFactory sqlSessionFactory, List<String> packageSearchPath,
                                int sleepSeconds) {
        PathMatchingResourcePatternResolver path_reolver = new PathMatchingResourcePatternResolver();
        this.mapperLocations = packageSearchPath.stream().flatMap(p -> {
            try {
                return Stream.of(path_reolver.getResources(p));
            } catch (IOException e) {
                return null;
            }
        }).filter(Objects::nonNull).collect(Collectors.toList());
        this.sqlSessionFactory = sqlSessionFactory;
        this.sleepSeconds = sleepSeconds;
        this.configuration = sqlSessionFactory.getConfiguration();
        this.run();
    }

    private volatile boolean in_do;

    private List<String> s_path = List.of("/src/main/java", "/src/main/resources");


    public Struct idea_gradle_find_src(File file) {
        Struct s = new Struct();

        String absolutePath = file.getAbsolutePath();
        s.setBuildFilePath(absolutePath);

        int r_res_path = absolutePath.lastIndexOf("/resources/main");
        int d = 15;
        if (r_res_path == -1) {
            d = 10;
            r_res_path = absolutePath.lastIndexOf("/resources");
        }
        String prefix = absolutePath.substring(0, r_res_path);
        String suffix = absolutePath.substring(r_res_path + d);
        int out_index = prefix.indexOf("/out/");
        if (out_index == -1) {
            out_index = prefix.lastIndexOf("/build");
        }
        if (out_index > -1) {
            String o = absolutePath.substring(0, out_index);
            for (String p : s_path) {
                File f = new File(o + p + suffix);
                if (f.exists()) {
                    s.setSrc(f);
                    s.setLast_time(f.lastModified());
                    s.setMd5(md5(f));
                    s.setName(f.getName());
                    break;
                }
            }
        }
        return s;
    }

    @Override
    public void run() {
//        final GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);
        final MybatisXmlHotRefresh runnable = this;
        new Thread(new Runnable() {
            private List<Struct> structs = new ArrayList<>();

            @Override
            public void run() {
                if (mapperLocations != null) {
                    for (Resource mapperLocation : mapperLocations) {
                        try {
                            if (ResourceUtils.isJarURL(mapperLocation.getURL())) {
                                String key = new UrlResource(ResourceUtils.extractJarFileURL(mapperLocation.getURL()))
                                        .getFile().getPath();
                                fileSet.add(key);
                                if (jarMapper.get(key) != null) {
                                    jarMapper.get(key).add(mapperLocation);
                                } else {
                                    List<Resource> resourcesList = new ArrayList<>();
                                    resourcesList.add(mapperLocation);
                                    jarMapper.put(key, resourcesList);
                                }
                            } else {
                                fileSet.add(mapperLocation.getFile().getPath());
                            }
                        } catch (IOException ioException) {
                            ioException.printStackTrace();
                        }
                    }
                }
                fileSet.parallelStream().forEach(f -> {
                    File file = new File(f);
                    structs.add(idea_gradle_find_src(file));
                });

                try {
                    Thread.sleep(delaySeconds * 1000);
                } catch (InterruptedException interruptedException) {
                    interruptedException.printStackTrace();
                }
                do {
                    if (!in_do) {
                        try {
                            for (Struct s : structs) {
                                if (s.newLastTime() > s.getLast_time()) {
                                    in_do = true;
                                    break;
                                }
                            }
                            if (in_do) {
                                List<Struct> collect = structs.stream().filter(f -> {
                                    String new_md5 = md5(f.getSrc());
                                    boolean b = !Objects.equals(f.md5, new_md5);
                                    if (b) {
                                        f.setLast_time(new File(f.getSrc().getAbsolutePath()).lastModified());
                                        f.setMd5(new_md5);
                                    }
                                    return b;
                                }).collect(Collectors.toList());
                                for (Struct filePath : collect) {
                                    List<Resource> removeList = jarMapper.get(filePath.getBuildFilePath());
                                    if (removeList != null && !removeList.isEmpty()) {
                                        for (Resource resource : removeList) {
                                            runnable.refresh(resource);
                                        }
                                    } else {
                                        runnable.refresh(new FileSystemResource(filePath.getSrc()));
                                    }
                                }
                            }
                        } catch (Exception exception) {
                            exception.printStackTrace();
                        } finally {
                            in_do = false;
                        }
                    }
                    try {
                        Thread.sleep(sleepSeconds * 1000);
                    } catch (InterruptedException interruptedException) {
                        interruptedException.printStackTrace();
                    }
                } while (true);

            }
        }, getClass().getName()).start();
    }

    @SneakyThrows
    public String md5(File file) {
        return MD5.encode(file);
    }

    @SneakyThrows
    @SuppressWarnings("rawtypes")
    private void refresh(Resource resource) {
        this.configuration = sqlSessionFactory.getConfiguration();
        boolean isSupper = configuration.getClass().getSuperclass() == Configuration.class;
        try {
            Field loadedResourcesField = isSupper ? configuration.getClass().getSuperclass().getDeclaredField("loadedResources")
                    : configuration.getClass().getDeclaredField("loadedResources");
            loadedResourcesField.setAccessible(true);
            Set loadedResourcesSet = ((Set) loadedResourcesField.get(configuration));
            XPathParser xPathParser = new XPathParser(resource.getInputStream(), true, configuration.getVariables(),
                    new XMLMapperEntityResolver());
            XNode context = xPathParser.evalNode("/mapper");
            String namespace = context.getStringAttribute("namespace");
            Field field = MapperRegistry.class.getDeclaredField("knownMappers");
            field.setAccessible(true);
            Map mapConfig = (Map) field.get(configuration.getMapperRegistry());
            Collection<String> mappedStatementNames = configuration.getMappedStatementNames();

            mapConfig.remove(Resources.classForName(namespace));
            loadedResourcesSet.remove(resource.toString());
            configuration.getCacheNames().remove(namespace);

            cleanParameterMap(context.evalNodes("/mapper/parameterMap"), namespace);
            cleanResultMap(context.evalNodes("/mapper/resultMap"), namespace);
            cleanKeyGenerators(context.evalNodes("insert|update|select"), namespace);
            cleanSqlElement(context.evalNodes("/mapper/sql"), namespace);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resource.getInputStream(),
                    sqlSessionFactory.getConfiguration(),
                    resource.toString(), sqlSessionFactory.getConfiguration().getSqlFragments());
            xmlMapperBuilder.parse();
            log.debug("热更新: {}, 成功!", resource.getFilename());
        } catch (IOException e) {
            log.error("Refresh IOException :" + e.getMessage());
        } finally {
            ErrorContext.instance().reset();
        }
    }

    private void cleanParameterMap(List<XNode> list, String namespace) {
        for (XNode parameterMapNode : list) {
            String id = parameterMapNode.getStringAttribute("id");
            configuration.getParameterMaps().remove(namespace + "." + id);
        }
    }

    private void cleanResultMap(List<XNode> list, String namespace) {
        for (XNode resultMapNode : list) {
            String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
            configuration.getResultMapNames().remove(id);
            configuration.getResultMapNames().remove(namespace + "." + id);
            clearResultMap(resultMapNode, namespace);
        }
    }

    private void clearResultMap(XNode xNode, String namespace) {
        for (XNode resultChild : xNode.getChildren()) {
            if ("association".equals(resultChild.getName()) || "collection".equals(resultChild.getName())
                    || "case".equals(resultChild.getName())) {
                if (resultChild.getStringAttribute("select") == null) {
                    configuration.getResultMapNames().remove(
                            resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));
                    configuration.getResultMapNames().remove(
                            namespace + "." + resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));
                    if (resultChild.getChildren() != null && !resultChild.getChildren().isEmpty()) {
                        clearResultMap(resultChild, namespace);
                    }
                }
            }
        }
    }

    private void cleanKeyGenerators(List<XNode> list, String namespace) {
        for (XNode context : list) {
            String id = context.getStringAttribute("id");
            configuration.getKeyGeneratorNames().remove(id + SelectKeyGenerator.SELECT_KEY_SUFFIX);
            configuration.getKeyGeneratorNames().remove(namespace + "." + id + SelectKeyGenerator.SELECT_KEY_SUFFIX);

            Collection<MappedStatement> mappedStatements = configuration.getMappedStatements();
            String k = namespace + "." + id;
            List<Object> objects = Stream.of(mappedStatements.toArray()).filter(p -> p instanceof MappedStatement && Objects.equals(((MappedStatement) p).getId(), k)).collect(Collectors.toList());
            mappedStatements.removeAll(objects);
        }
    }

    private void cleanSqlElement(List<XNode> list, String namespace) {
        for (XNode context : list) {
            String id = context.getStringAttribute("id");
            configuration.getSqlFragments().remove(id);
            configuration.getSqlFragments().remove(namespace + "." + id);
        }
    }

    @Data
    @Accessors(chain = true)
    @EqualsAndHashCode(callSuper = false)
    private static class Struct {
        private String buildFilePath;
        private String name;
        private File src;
        private String md5;
        private long last_time;

        public long newLastTime() {
            return new File(src.getAbsolutePath()).lastModified();
        }

    }
}

其中的md5类

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5 {

    public static String encode(String str) {
        return encode(str.getBytes(StandardCharsets.UTF_8));
    }

    public static String encode(byte[] bytes) {
        try {
            return toHash(MessageDigest.getInstance("MD5").digest(bytes));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    public static String toHash(byte[] bytes) {
        StringBuffer hex = new StringBuffer();
        for (byte b : bytes) {
            int val = ((int) b) & 0xff;
            if (val < 16) {
                hex.append("0");
            }
            hex.append(Integer.toHexString(val));
        }
        return hex.toString();
    }

    public static String encode(File file) throws IOException {
        return encode(FileUtils.readFileToByteArray(file));
    }

    public static String encode(String salt, String str) {
        try {
            return encode(salt.getBytes("UTF-8"), str.getBytes("utf-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    public static String encode(byte[] salt, byte[] str) {
        try {
            MessageDigest m = MessageDigest.getInstance("MD5");
            m.update(salt);
            m.update(str);
            return toHash(m.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

}

本文地址:https://blog.csdn.net/qq_23668595/article/details/107353737