这几天发现单位同事都在使用JRebel作为热部署工具,它集合多个容器,能够不在重启的情况下进行部署。我们在开发过程中,经常碰到改动xml等配置文件的时候,需要进行重新启动容器,重而带来开发进度缓慢。
我是一个传统的人,感觉WTP部署已经够强悍,但是看着90后小朋友玩这么酷炫的万一,让我由衷的觉得更加无聊的事情,不就是能够热部署,大不了写一个定时任务时不时的去加载xml文件,一直检测文件的最后修改,然后重新加载吗?Tomcat的reload是启动后台线程加载这,这种做法挺好的。
但是以下这种作法依托于spring,感觉比较容易配置,值得去倡导。
public class XMLMapperLoader implements DisposableBean, InitializingBean, ApplicationContextAware { private static final Log logger = LogFactory.getLog(XMLMapperLoader.class); private ConfigurableApplicationContext context = null; private transient String basePackage = null; private final HashMap<String, String> fileMapping = new HashMap<String, String>(); private Scanner scanner = null; private ScheduledExecutorService service = null; @Override public void setApplicationContext( ApplicationContext applicationContext ) throws BeansException { context = (ConfigurableApplicationContext) applicationContext; } public void setBasePackage( String basePackage ) { this.basePackage = basePackage; } @Override public void afterPropertiesSet() throws Exception { try { Environment environment = EnvironmentDetect.detectEnvironment(); if ( environment.isProduct() || environment.isTest() ) { return; } // Assert.notNull(sqlSessionFactory, "sqlSessionFactory不能为null"); service = Executors.newScheduledThreadPool(1); // 获取xml所在包 if ( basePackage == null ) { MapperScannerConfigurer config = context.getBean(MapperScannerConfigurer.class); Field field = config.getClass().getDeclaredField("basePackage"); field.setAccessible(true); basePackage = (String) field.get(config); } // 触发文件监听事件XMLMapperEntityResolver 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() ) { logger.debug("*Mapper.xml文件改变,重新加载."); scanner.reloadXML(); logger.debug("加载完毕."); } } catch ( Exception e ) { logger.error(e.getMessage(), e); } } } @SuppressWarnings({ "rawtypes" }) class Scanner { private final String[] basePackages; private static final String XML_RESOURCE_PATTERN = "**/*.xml"; private final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); public Scanner() { basePackages = StringUtils.tokenizeToStringArray(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 { SqlSessionFactory factory = context.getBean(SqlSessionFactory.class); Configuration configuration = factory.getConfiguration(); // 移除加载项 removeConfig(configuration); // 重新扫描加载 for ( String basePackage : basePackages ) { Resource[] resources = getResource(basePackage, XML_RESOURCE_PATTERN); if ( resources != null ) { for ( Resource resource : resources ) { if ( resource == null ) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resource.getInputStream(), configuration, resource.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch ( Exception e ) { throw new NestedIOException("Failed to parse mapping resource: '" + resource + "'", 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 ( Resource resource : resources ) { String multi_key = getValue(resource); fileMapping.put(resource.getFilename(), 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 ( Resource resource : resources ) { String name = resource.getFilename(); String value = fileMapping.get(name); String multi_key = getValue(resource); if ( !multi_key.equals(value) ) { isChanged = true; fileMapping.remove(name); fileMapping.put(name, multi_key); } } } } return isChanged; } } @Override public void destroy() throws Exception { if ( service != null ) { service.shutdownNow(); } } }
Spring的配置:
<bean id="*********XmlMapper"/>
个人结论:
类似的作法其实都差不多,都是检测文件,但是一直想做一个类似于mysql的驱动插件,能够把mysql的文件变动的内容,直接push过来,而不是pull来。