MyBatis插件:xml文件修改自动加载,减少不必要的重启

1,007 阅读1分钟

热部署插件

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;

import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@ConditionalOnProperty(name = "mybatis.refresh", havingValue = "true")
@Component
@Slf4j
public class MybatisMapperRefresh{

    @Autowired
    @Qualifier("sqlSessionFactory")
    private SqlSessionFactory sqlSessionFactory;

    private Resource[] mapperLocations;

    private String packageSearchPath = "classpath*:/builder/**Mapper.xml";

    private boolean isEnable = true; // 是否启用刷新线程

    private int deplaySeconds = 3; // 刷新延迟

    private List<String> changeList;

    private HashMap<String, Long> fileMapping = new HashMap<String, Long>();// 记录文件是否变化

    /**
     * 初始化方法
     */
    @PostConstruct
    public void init() {
        if (!isEnable) {
            return;
        }

        final MybatisMapperRefresh ruRefresh = this;
        new Thread(new Runnable() {
            public void run() {
                while (true) {
                    try {
                        ruRefresh.refreshMapper();
                    } catch (Exception e1) {
                        log.error("刷新失败");
                    }

                    try {
                        Thread.sleep(deplaySeconds * 1000);
                    } catch (InterruptedException e) {
                        log.error("刷新失败");
                    }
                }
            }
        }, "Thread-Mybatis-Refresh").start();

    }

    public void refreshMapper() {
        try {
            Configuration configuration = this.sqlSessionFactory.getConfiguration();

            try {
                this.scanMapperXml();
            } catch (IOException e) {
                log.error("扫描包路径配置错误");
                return;
            }

            if (this.isChanged()) {
                this.removeConfig(configuration);

                for (Resource configLocation : mapperLocations) {
                    try {
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configLocation.getInputStream(),
                                configuration, configLocation.toString(), configuration.getSqlFragments());
                        xmlMapperBuilder.parse();
                        if (changeList.contains(configLocation.getFilename()))
                            log.info("[" + configLocation.getFilename() + "] refresh finished");
                    } catch (IOException e) {
                        log.error("[" + configLocation.getFilename() + "] error");
                        continue;
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void setPackageSearchPath(String packageSearchPath) {
        this.packageSearchPath = packageSearchPath;
    }

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    /**
     * 扫描xml文件所在的路径
     *
     * @throws IOException
     */
    private void scanMapperXml() throws IOException {
        this.mapperLocations = new PathMatchingResourcePatternResolver().getResources(packageSearchPath);
    }

    /**
     * 清空Configuration中几个重要的缓存
     *
     * @param configuration
     * @throws Exception
     */
    private void removeConfig(Configuration configuration) throws Exception {
        Class<?> classConfig = configuration.getClass();
        clearMap(classConfig, configuration, "mappedStatements");
        clearMap(classConfig, configuration, "caches");
        clearMap(classConfig, configuration, "resultMaps");
        clearMap(classConfig, configuration, "parameterMaps");
        clearMap(classConfig, configuration, "keyGenerators");
        clearMap(classConfig, configuration, "sqlFragments");

        clearSet(classConfig, configuration, "loadedResources");

    }

    private void clearMap(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
        Field field = classConfig.getDeclaredField(fieldName);
        field.setAccessible(true);
        Map mapConfig = (Map) field.get(configuration);
        mapConfig.clear();
    }

    private void clearSet(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
        Field field = classConfig.getDeclaredField(fieldName);
        field.setAccessible(true);
        Set setConfig = (Set) field.get(configuration);
        setConfig.clear();
    }

    /**
     * 判断文件是否发生了变化
     *
     * @param resource
     * @return
     * @throws IOException
     */
    private boolean isChanged() throws IOException {
        boolean flag = false;
        changeList = new ArrayList<String>();
        for (Resource resource : mapperLocations) {
            String resourceName = resource.getFilename();

            boolean addFlag = !fileMapping.containsKey(resourceName);// 此为新增标识

            // 修改文件:判断文件内容是否有变化
            Long compareFrame = fileMapping.get(resourceName);
            long lastFrame = resource.contentLength() + resource.lastModified();
            boolean modifyFlag = null != compareFrame && compareFrame.longValue() != lastFrame;// 此为修改标识

            // 新增或是修改时,存储文件
            if (addFlag || modifyFlag) {
                fileMapping.put(resourceName, Long.valueOf(lastFrame));// 文件内容帧值
                flag = true;
                changeList.add(resource.getFilename());
            }
        }
        return flag;
    }

    public int getDeplaySeconds() {
        return deplaySeconds;
    }

    public void setDeplaySeconds(int deplaySeconds) {
        this.deplaySeconds = deplaySeconds;
    }

    public boolean isEnable() {
        return isEnable;
    }

    public void setIsEnable(boolean isEnable) {
        this.isEnable = isEnable;
    }

}