package com.baidu.disconf.client.addons.properties; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import com.baidu.disconf.client.DisconfMgr; /** * A properties factory bean that creates a reconfigurable Properties object. * When the Properties' reloadConfiguration method is called, and the file has * changed, the properties are read again from the file. * <p/> * 真正的 reload bean 定义,它可以定义多个 resource 为 reload config file */ public class ReloadablePropertiesFactoryBean extends PropertiesFactoryBean implements DisposableBean, ApplicationContextAware { private static ApplicationContext applicationContext; protected static final Logger log = LoggerFactory.getLogger(ReloadablePropertiesFactoryBean.class); private Resource[] locations; private long[] lastModified; private List<IReloadablePropertiesListener> preListeners; /** * 定义资源文件 * * @param fileNames */ public void setLocation(final String fileNames) { List<String> list = new ArrayList<String>(); list.add(fileNames); setLocations(list); } /** */ public void setLocations(List<String> fileNames) { List<Resource> resources = new ArrayList<Resource>(); for (String filename : fileNames) { // trim filename = filename.trim(); String realFileName = getFileName(filename); // // register to disconf // DisconfMgr.getInstance().reloadableScan(realFileName); // // only properties will reload // String ext = FilenameUtils.getExtension(filename); if (ext.equals("properties")) { PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver(); try { Resource[] resourceList = pathMatchingResourcePatternResolver.getResources(filename); for (Resource resource : resourceList) { resources.add(resource); } } catch (IOException e) { e.printStackTrace(); } } } this.locations = resources.toArray(new Resource[resources.size()]); lastModified = new long[locations.length]; super.setLocations(locations); } /** * get file name from resource * * @param fileName * * @return */ private String getFileName(String fileName) { if (fileName != null) { int index = fileName.indexOf(':'); if (index < 0) { return fileName; } else { fileName = fileName.substring(index + 1); index = fileName.lastIndexOf('/'); if (index < 0) { return fileName; } else { return fileName.substring(index + 1); } } } return null; } protected Resource[] getLocations() { return locations; } /** * listener , 用于通知回调 * * @param listeners */ public void setListeners(final List listeners) { // early type check, and avoid aliassing this.preListeners = new ArrayList<IReloadablePropertiesListener>(); for (Object o : listeners) { preListeners.add((IReloadablePropertiesListener) o); } } private ReloadablePropertiesBase reloadableProperties; /** * @return * * @throws IOException */ @Override protected Properties createProperties() throws IOException { return (Properties) createMyInstance(); } /** * createInstance 废弃了 * * @throws IOException */ protected Object createMyInstance() throws IOException { // would like to uninherit from AbstractFactoryBean (but it's final!) if (!isSingleton()) { throw new RuntimeException("ReloadablePropertiesFactoryBean only works as singleton"); } // set listener reloadableProperties = new ReloadablePropertiesImpl(); if (preListeners != null) { reloadableProperties.setListeners(preListeners); } // reload reload(true); // add for monitor ReloadConfigurationMonitor.addReconfigurableBean((ReconfigurableBean) reloadableProperties); return reloadableProperties; } public void destroy() throws Exception { reloadableProperties = null; } /** * 根据修改时间来判定是否reload * * @param forceReload * * @throws IOException */ protected void reload(final boolean forceReload) throws IOException { boolean reload = forceReload; for (int i = 0; i < locations.length; i++) { Resource location = locations[i]; File file; try { file = location.getFile(); } catch (IOException e) { // not a file resource // may be spring boot log.warn(e.toString()); continue; } try { long l = file.lastModified(); if (l > lastModified[i]) { lastModified[i] = l; reload = true; } } catch (Exception e) { // cannot access file. assume unchanged. if (log.isDebugEnabled()) { log.debug("can't determine modification time of " + file + " for " + location, e); } } } if (reload) { doReload(); } } /** * 设置新的值 * * @throws IOException */ private void doReload() throws IOException { reloadableProperties.setProperties(mergeProperties()); } /** * @return */ @Override public String toString() { return this.getClass().getSimpleName(); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** * 回调自己 */ class ReloadablePropertiesImpl extends ReloadablePropertiesBase implements ReconfigurableBean { // reload myself public void reloadConfiguration() throws Exception { ReloadablePropertiesFactoryBean.this.reload(false); } } }