这几天发现单位同事都在使用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来。