package com.jkurrent.framework.mybatis; import com.jkurrent.framework.service.sqlmap.impl.SqlMapFactoryBean; import org.apache.ibatis.builder.xml.XMLMapperBuilder; import org.apache.ibatis.executor.ErrorContext; import org.apache.ibatis.session.Configuration; import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.NestedIOException; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import java.io.IOException; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * 監聽MyBatis的Mapper.xml的修改,自動刷新配置 * * @author wuzhilang * @version V1.00 */ public class XMLMapperLoader implements DisposableBean, InitializingBean, ApplicationContextAware { private ConfigurableApplicationContext context = null; private transient String basePackage = null; private HashMap<String, String> fileMapping = new HashMap<String, String>(); private Scanner scanner = null; private ScheduledExecutorService service = null; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = (ConfigurableApplicationContext) applicationContext; } @Override public void afterPropertiesSet() throws Exception { try { service = Executors.newScheduledThreadPool(1); // 獲取xml所在包 basePackage = "sqlmap"; // 觸發文件監聽事件 scanner = new Scanner(); scanner.scan(); service.scheduleAtFixedRate(new Task(), 5, 5, TimeUnit.SECONDS); } catch (Exception e1) { e1.printStackTrace(); } } class Task implements Runnable { @Override public void run() { try { if (scanner.isChanged()) { // System.out.println("*_SqlMap.xml文件改變,從新加載."); scanner.reloadXML(); System.out.println("加載完畢."); } } catch (Exception e) { e.printStackTrace(); } } } @SuppressWarnings({"rawtypes"}) class Scanner { private String[] basePackages; private static final String XML_RESOURCE_PATTERN = "**\\*_SqlMap.xml"; private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); public Scanner() { basePackages = StringUtils.tokenizeToStringArray(XMLMapperLoader.this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); } public Resource[] getResource(String basePackage, String pattern) throws IOException { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(context.getEnvironment().resolveRequiredPlaceholders( basePackage)) + "/" + pattern; Resource[] resources = resourcePatternResolver.getResources(packageSearchPath); return resources; } public void reloadXML() throws Exception { Set<Map.Entry<String, SqlMapFactoryBean>> set = context.getBeansOfType(SqlMapFactoryBean.class).entrySet(); for(Map.Entry<String, SqlMapFactoryBean> entry : set) { System.out.println("更新數據源:" + entry); SqlMapFactoryBean factory = entry.getValue(); Configuration configuration = factory.getSqlSessionFactory().getConfiguration(); // 移除加載項 removeConfig(configuration); // 從新掃描加載 for (String basePackage : basePackages) { Resource[] resources = getResource(basePackage, XML_RESOURCE_PATTERN); if (resources != null) { for (int i = 0; i < resources.length; i++) { if (resources[i] == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resources[i].getInputStream(), configuration, resources[i].toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + resources[i] + "'", e); } finally { ErrorContext.instance().reset(); } } } } } } 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(); } public void scan() throws IOException { if (!fileMapping.isEmpty()) { return; } for (String basePackage : basePackages) { Resource[] resources = getResource(basePackage, XML_RESOURCE_PATTERN); if (resources != null) { for (int i = 0; i < resources.length; i++) { String multi_key = getValue(resources[i]); fileMapping.put(resources[i].getFile().getPath(), multi_key); } } } } private String getValue(Resource resource) throws IOException { String contentLength = String.valueOf((resource.contentLength())); String lastModified = String.valueOf((resource.lastModified())); return new StringBuilder(contentLength).append(lastModified).toString(); } public boolean isChanged() throws IOException { boolean isChanged = false; for (String basePackage : basePackages) { Resource[] resources = getResource(basePackage, XML_RESOURCE_PATTERN); if (resources != null) { for (int i = 0; i < resources.length; i++) { String name = resources[i].getFile().getPath(); String value = fileMapping.get(name); String multi_key = getValue(resources[i]); if (!multi_key.equals(value)) { System.out.println(name + "文件改變, 準備從新加載"); isChanged = true; fileMapping.put(name, multi_key); } } } } return isChanged; } } @Override public void destroy() throws Exception { if (service != null) { service.shutdownNow(); } } public String getBasePackage() { return basePackage; } public void setBasePackage(String basePackage) { this.basePackage = basePackage; } }