1. 程式人生 > >Spring專案中對XML檔案熱載入程式碼實現

Spring專案中對XML檔案熱載入程式碼實現

前言:

平時我們進行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的擴充套件多作認識。