论坛首页 编程语言技术论坛

依托于Spring重加载

浏览 2454 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2015-10-05  

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

 

 

 

   发表时间:2015-10-12  
楼主的做法能支持Spring配置文件的实时重新载,想法很新颖,到是可以借鉴一下。
只是跟JRebel的热部署相比,功能上还有所欠缺,JRebel是可以实现类文件的热加载,这样可以省去应该重新启动的损耗。
但就像JRebel自己说过的,考虑到性能问题,这个热部署主要还是适用于测试环境下,可以避免不停的重启测试服务器,但在生产机上,除非完全不用考虑性能问题,否则还是慎用。
0 请登录后投票
   发表时间:2015-10-12  
Gary_Jiao 写道
楼主的做法能支持Spring配置文件的实时重新载,想法很新颖,到是可以借鉴一下。
只是跟JRebel的热部署相比,功能上还有所欠缺,JRebel是可以实现类文件的热加载,这样可以省去应该重新启动的损耗。
但就像JRebel自己说过的,考虑到性能问题,这个热部署主要还是适用于测试环境下,可以避免不停的重启测试服务器,但在生产机上,除非完全不用考虑性能问题,否则还是慎用。

     觉得可以提炼出来做开源产品
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics