Spring專案中對XML檔案熱載入程式碼實現
阿新 • • 發佈:2019-01-08
前言:
平時我們進行Spring專案開發的時候,經常需要因為XML的改變而重啟伺服器,假如專案較大的時候,重啟將會非常耗時。最近編寫的webschool框架需要用到較多的XML檔案進行配置,在這裡參考公司程式碼,實現了對XML檔案的熱載入。
效果
每次修改xml檔案後,Spring都會對xml進行重新讀取,自動覆蓋原來的bean。
實現思路:
在Spring中建立一個執行緒不斷監控我們匯入的xml檔案是否已修改,一旦修改,馬上過載。
繼承擴充套件XmlWebApplicationContext類
/**
* declaration:
* 擴充套件Xbean包中的XmlWebApplication類,使之支援熱載入。
*
* author wenkangqiang
* date 2016年3月5日
*/
public class XmlRefreshWebApplicationContext extends XmlWebApplicationContext {
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
private static String springClassPathPrefix = "classpath:";
public void reload() {
//重新獲得web.xml檔案中"contextConfigLocation"引數(被載入的bean目錄)
String location = this.getServletContext().getInitParameter(
CONFIG_LOCATION_PARAM);
//通過特定的符號,;\n對引數進行分割,得出各個的路徑
String[] locations = StringUtils.tokenizeToStringArray(location,
CONFIG_LOCATION_DELIMITERS);
//獲得被修改過的location,然後往下再重新載入那些被修改過的xml檔案
List<String> moidifiedLocationList = getModifiedLocation(locations);
if (moidifiedLocationList != null && moidifiedLocationList.size() != 0) {
this.setConfigLocations(StringUtils
.toStringArray(moidifiedLocationList));
doRefresh();
}
}
/**
* 鑑定資原始檔(*.xml)是否已經發生更改
* @param locations
* @return
*/
public List<String> getModifiedLocation(String[] locations) {
List<String> refreshLocationList = new ArrayList<String>();
List<NoCacheClassPathResource> actualResources = new ArrayList<NoCacheClassPathResource>();
ConfigFileModifiedFactory fileModifiedFactory = ConfigFileModifiedFactory
.getInstance();
try {
/*
* 獲得資原始檔物件(Resource)
*/
for (int i = 0; i < locations.length; i++) {
Resource[] resources = ((ResourcePatternResolver) this)
.getResources(locations[i]);
for (int j = 0; j < resources.length; j++) {
actualResources
.add((NoCacheClassPathResource) resources[j]);
}
}
/*
* 通過逐個比較資源的最近修改時間來判斷檔案是否已經更改
*/
for (NoCacheClassPathResource resource : actualResources) {
Long fileLastModified = fileModifiedFactory.get(resource
.getPath());
if (fileLastModified != null
&& fileLastModified != resource.lastModified()) {
//已經發生修改,就把路徑寫入refreshLocationList的名單中,稍後reload
refreshLocationList.add(springClassPathPrefix
+ resource.getPath());
}
//更新所有資原始檔的最近修改時間
fileModifiedFactory.put(resource.getPath(), resource
.lastModified());
}
} catch (Exception ex) {
ex.printStackTrace();
}
return refreshLocationList;
}
public void doRefresh() {
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) this
.getBeanFactory();
try {
this.loadBeanDefinitions(beanFactory);
finishBeanFactoryInitialization(beanFactory);
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 根據路徑獲得資原始檔
* (non-Javadoc)
* @see org.springframework.core.io.DefaultResourceLoader#getResource(java.lang.String)
*/
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new NoCacheClassPathResource(location
.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
} else {
try {
URL url = new URL(location);
return new UrlResource(url);
} catch (MalformedURLException ex) {
return getResourceByPath(location);
}
}
}
}
編寫一個類,專門儲存path檔案的最近的修改時間
/**
* declaration:
* 專門用於儲存資原始檔path : 修改時間的工廠類,實質上是維護著一個map
* author wenkangqiang
* date 2016年3月5日
*/
public class ConfigFileModifiedFactory {
private static ConfigFileModifiedFactory configFactory = new ConfigFileModifiedFactory();
private Map<String, Long> fileModifiedMap = new HashMap<String, Long>();
public static ConfigFileModifiedFactory getInstance() {
return configFactory;
}
public void put(String key, long lastModified) {
fileModifiedMap.put(key, lastModified);
}
public Long get(String key) {
return fileModifiedMap.get(key);
}
}
無快取匯入類:NoCacheClassPathResource.java
/**
* declaration:
* 路徑指定資原始檔,通過路徑去獲得資原始檔的流
* author wenkangqiang
* date 2016年3月5日
*/
public class NoCacheClassPathResource extends ClassPathResource {
public NoCacheClassPathResource(String path, ClassLoader classLoader) {
super(path, classLoader);
}
public InputStream getInputStream() throws IOException {
InputStream is = null;
is = this.getClassLoader().getResource(this.getPath()).openStream();
return is;
}
}
定義探測的執行緒
/**
* declaration:
* 設計執行緒,保持對資原始檔的不斷探測
* author wenkangqiang
* date 2016年3月5日
*/
public class SpringBeanReloadJob implements Runnable {
private static final Logger log = LogManager
.getLogger(SpringBeanReloadJob.class);
@Override
public void run() {
try{
XmlRefreshWebApplicationContext applicationContext = (XmlRefreshWebApplicationContext)ContextLoader
.getCurrentWebApplicationContext();
applicationContext.reload();
} catch (Exception ex) {
log.error("Sping過載配置失敗", ex);
}
}
}
在Spring的ApplicationContext檔案中定義執行緒
<!-- XML檔案熱載入 使用JDK ScheduledExecutorService的定時任務配置 -->
<!-- ScheduledThreadPoolExecutor整合工廠 -->
<bean class="org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean" lazy-init="false">
<property name="continueScheduledExecutionAfterException" value="true" />
<property name="scheduledExecutorTasks">
<list>
<ref bean="scheduledExecutorTasks" />
</list>
</property>
</bean>
<!-- Task的封裝 -->
<bean id="scheduledExecutorTasks" class="org.springframework.scheduling.concurrent.ScheduledExecutorTask">
<property name="runnable" ref="executorJob" />
<!-- 首次執行延期2秒 -->
<property name="delay" value="10000" />
<property name="period" value="2000" />
<!-- 固定間隔,否則預設fixDelay會等到前一個任務完成後才開始計時. -->
<property name="fixedRate" value="true" />
</bean>
<bean id="executorJob" class="com.cn.qpm.framework.springdynaload.SpringBeanReloadJob" />
總結
至此,基於Spring的XML熱載入的程式碼已經全部貼出來了。原理並不難,不過要編寫出來還需對Spring的擴充套件多作認識。