原代码来自网络, 重组了代码并优化了isChanged的判断 (删除/增加/更新xml均能侦测), 此热加载工具可以脱离spring容器运行, 有利于加快junit调试.
package com.freestyle.common.db.mybatis; 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.Map.Entry; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.ibatis.builder.xml.XMLMapperBuilder; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.log4j.Logger; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; /** * mybatis的mapper文件有改动时,进行手动重新加载 * @author dgmislrh */ public class MybatisReloader { private Logger log = Logger.getLogger(MybatisReloader.class); private Resource[] mLastmapperLocations = null; private Map<Resource, Long> mvLastFingerPrint = null; private String packageSearchPath=null; SqlSessionFactory sqlSessionFactory; Configuration configuration; public MybatisReloader(String pvsPackageSearchPath, SqlSessionFactory pvSqlSessionFactory) { this.packageSearchPath = pvsPackageSearchPath; this.configuration = pvSqlSessionFactory.getConfiguration(); this.sqlSessionFactory = pvSqlSessionFactory; try { mLastmapperLocations = this.scanMapperXml(); mvLastFingerPrint = getFingerPrint(mLastmapperLocations); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // private HashMap<String, Long> fileMapping = new HashMap<String, // Long>();// 记录文件是否变化 private static Map<Resource, Long> getFingerPrint(Resource[] pvResources) { Map<Resource, Long> lvRet = new HashMap<Resource, Long>(); for (Resource item : pvResources) { Long lvValue = 0L; try { lvValue = item.contentLength() + item.lastModified(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } lvRet.put(item, lvValue); } return lvRet; } public String refreshMapper(boolean pvbRunOnce) throws Exception { StringBuilder sb = new StringBuilder(); try { Runnable runnable = new Runnable() { public void run() { // task to run goes here try { // 判断是否有文件发生了变化 List<Resource> lvToDelete = new ArrayList<Resource>(); List<Resource> lvToAdd= new ArrayList<Resource>(); if (isChanged(lvToDelete,lvToAdd)) { // 清理 removeConfig(configuration); // 重新加载 for (Resource configLocation : mLastmapperLocations) { try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder( configLocation.getInputStream(), configuration, configLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); // log.info("mapper文件[" + // configLocation.getFilename() + "]加载成功"); sb.append("INFO: mapper文件[" + configLocation.getFilename() + "]加载成功\n"); } catch (IOException e) { // log.error("mapper文件[" + // configLocation.getFilename() + // "]不存在或内容格式不对"); sb.append("ERROR: mapper文件[" + configLocation.getFilename() + "]不存在或内容格式不对\n"); continue; } } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * * 判断文件是否发生了变化 * @param resource * @return * @throws * IOException */ boolean isChanged(List<Resource> pvToDelete, List<Resource> pvToAdd) throws IOException { boolean flag = false; /* * for (Resource resource : mapperLocations) { String * resourceName = resource.getFilename(); boolean addFlag = * !fileMapping.isEmpty() && * !fileMapping.containsKey(resourceName);// 此为新增标识 // * 修改文件:判断文件内容是否有变化 Long compareFrame = * fileMapping.get(resourceName); long lastFrame = * resource.contentLength() + resource.lastModified(); * boolean modifyFlag = null != compareFrame && * compareFrame.longValue() != lastFrame;// 此为修改标识 * * fileMapping.put(resourceName, Long.valueOf(lastFrame));// * 文件内容帧值 // 新增或是修改时,存储文件 if(addFlag || modifyFlag) { flag = * true; } } */ // 获得当前的状态 Resource[] lvLastmapperLocations = scanMapperXml(); Map<Resource, Long> lvLastFingerPrint = getFingerPrint(lvLastmapperLocations); // 判断需要删除和更新的 for (Entry<Resource, Long> item : mvLastFingerPrint.entrySet()) { if (!lvLastFingerPrint.containsKey(item.getKey())) { pvToDelete.add(item.getKey()); } else { Long lvLast = lvLastFingerPrint.get(item.getKey()); if (!lvLast.equals(item.getValue())) { // 有变动 pvToDelete.add(item.getKey()); pvToAdd.add(item.getKey()); } } } // 判断增加的 for (Entry<Resource, Long> item : lvLastFingerPrint.entrySet()) { if (!lvLastFingerPrint.containsKey(item.getKey())) { pvToAdd.add(item.getKey()); } } mLastmapperLocations=lvLastmapperLocations; mvLastFingerPrint=lvLastFingerPrint; return pvToAdd.size() > 0 || pvToDelete.size() > 0; } }; if (!pvbRunOnce) { ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); // 第二个参数为首次执行的延时时间,第三个参数为定时执行的间隔时间 service.scheduleAtFixedRate(runnable, 1, 10, TimeUnit.SECONDS); return ""; } else { runnable.run(); return sb.toString(); } } catch (Exception e) { e.printStackTrace(); } return ""; } public void setPackageSearchPath(String packageSearchPath) { this.packageSearchPath = packageSearchPath; } /** * 扫描xml文件所在的路径 * @throws IOException */ private Resource[] scanMapperXml() throws IOException { return new PathMatchingResourcePatternResolver().getResources(packageSearchPath); } /** * 清空Configuration中几个重要的缓存 * @param configuration * @throws Exception */ protected static 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"); } @SuppressWarnings("rawtypes") private static 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(); } @SuppressWarnings("rawtypes") private static 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(); } }
写一个junit测试:
package test; import java.util.List; import java.util.Map; import org.apache.ibatis.session.SqlSession; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.logicalcobwebs.proxool.ProxoolFacade; import com.freestyle.common.db.mybatis.MybatisReloader; import com.freestyle.common.db.mybatis.MybatisUtils; import com.freestyle.common.db.proxool.ProxoolUtils; import com.freestyle.proxoolmybatisstudy.dao.TaUserMapper; import com.freestyle.proxoolmybatisstudy.entities.TaUser; public class Test1 { @Before public void setUp() throws Exception { //加载Proxool数据库连接池 ProxoolUtils.loadProxool(); } @After public void tearDown() throws Exception { ProxoolFacade.shutdown(0); } @Test public void testReload() throws Exception{ MybatisReloader lvReloader=new MybatisReloader("classpath:com/freestyle/proxoolmybatisstudy/dao/*.xml",MybatisUtils.getSessionFactory()); test(); System.out.print(lvReloader.refreshMapper(true)); test(); }
如果mapper没更改,则lvReloader.refreshMapper(true)返回空字符串"",如果有更改,则会返回重新加载的日志信息.
我们在lvReloader.refreshMapper(true)处打个断点, 运行到这里的时候 , 更改TaUserMapper.xml, 再接着跑下去,日志输出 :
11:15:46,050 DEBUG PathMatchingResourcePatternResolver:693 - Searching directory [C:\myproject\workspace\proxoolmybatis\target\classes\com\freestyle\proxoolmybatisstudy\dao] for files matching pattern [C:/myproject/workspace/proxoolmybatis/target/classes/com/freestyle/proxoolmybatisstudy/dao/*.xml] 11:15:46,053 DEBUG PathMatchingResourcePatternResolver:424 - Resolved location pattern [classpath:com/freestyle/proxoolmybatisstudy/dao/*.xml] to resources [file [C:\myproject\workspace\proxoolmybatis\target\classes\com\freestyle\proxoolmybatisstudy\dao\TaUserMapper.xml], file [C:\myproject\workspace\proxoolmybatis\target\classes\com\freestyle\proxoolmybatisstudy\dao\TdNotifyMapper.xml]] INFO: mapper文件[TaUserMapper.xml]加载成功 INFO: mapper文件[TdNotifyMapper.xml]加载成功