十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
这期内容当中小编将会给大家带来有关springboot2 中怎么动态加载properties 文件,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。
创新互联公司服务项目包括龙城网站建设、龙城网站制作、龙城网页制作以及龙城网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,龙城网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到龙城省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!
1、比较好的方案,采用文件监控 依赖 commons-io2
commons-io commons-io 2.5
2、编写监听器
import java.io.File; import com.dingxianginc.channelaggregation.webconfig.properties.PropertyConfig; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; import org.apache.commons.io.monitor.FileAlterationObserver; import org.springframework.beans.BeansException; import org.springframework.stereotype.Component; /** * 自定义文件监听器 * @author ysma */ @Slf4j @Component public class FileListener extends FileAlterationListenerAdaptor{ private PropertyConfig propertyConfig; private String configDir; public FileListener() { } public FileListener(PropertyConfig propertyConfig, String configDir) { this.propertyConfig = propertyConfig; this.configDir = configDir; } @Override public void onStart(FileAlterationObserver observer) { log.debug("FileListener 启动 observer:{}", observer.toString()); } @Override public void onDirectoryCreate(File directory) { log.info("FileListener [新建]:path:{}", directory.getPath()); } @Override public void onDirectoryChange(File directory) { log.info("FileListener [修改]:path:{}", directory.getPath()); } @Override public void onDirectoryDelete(File directory) { log.info("FileListener [删除]:path:{}", directory.getPath()); } @Override public void onStop(FileAlterationObserver observer) { log.debug("FileListener 停止 observer:{}", observer.toString()); } @Override public void onFileCreate(File file) { log.info("FileListener [新建]:path:{}", file.getPath()); refreshProperties(); log.info("{}-文件新增,重新加载配置文件:{}", file.getName(), configDir); } @Override public void onFileChange(File file) { log.info("FileListener [修改]:path:{}", file.getPath()); if(file.getName().endsWith("properties")){ log.info("文件修改,重新加载配置文件:{}", file.getName()); refreshProperties(file.getPath()); } } @Override public void onFileDelete(File file) { log.info("FileListener [删除]:path:{}", file.getPath()); refreshProperties(); log.info("{}-文件删除,重新加载配置文件:{}", file.getName(), configDir); } private void refreshProperties(String... filePaths){ try { if(propertyConfig == null){ log.error("FileListener.refreshProperties propertyConfig 获取失败,无法刷新properties配置文件"); } else { if(filePaths.length > 0){ //修改文件 重新加载该文件 String filePath = filePaths[0]; int index = filePath.indexOf(configDir);//定位config目录的位置 String diyPath = filePath.substring(index); propertyConfig.loadPoint(diyPath); } else { //新增删除文件 控制overview.properties进行重新加载 propertyConfig.init(); } } } catch (BeansException e) { log.error("FileListener 刷新配置文件失败,path:{}", filePaths, e); } } }
3、编写配置类
import com.dingxianginc.channelaggregation.util.VariableConfig; import com.dingxianginc.channelaggregation.webconfig.properties.PropertyConfig; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.monitor.FileAlterationMonitor; import org.apache.commons.io.monitor.FileAlterationObserver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; import java.io.File; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * @author ysma 2019-07-30 * 监控 resouces目录下文件的变化 */ @Slf4j @Configuration public class FileListenerConfig { // 轮询间隔 5 秒 private static final long INTERVAL = TimeUnit.SECONDS.toMillis(5); @Autowired private PropertyConfig propertyConfig; @Autowired private VariableConfig variableConfig; @PostConstruct public void init(){ //path尾缀 / String path = Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource("")).getPath(); String configDir = variableConfig.getConfigDir(); String dir = configDir.startsWith("/")? path + configDir.substring(1) : path + configDir; monitor(dir); } /** * 目录和文件监控:遍历文件夹 递归监控 * @param dir 目录 */ public void monitor(String dir){ File resource = new File(dir); if (resource.isDirectory()){ File[] files = resource.listFiles(); if(files != null){ for(File file : files){ monitor(file.getPath()); } } log.info("监听文件目录:{}", dir); monitorFile(dir); } } /** * 监听文件变化 */ public void monitorFile(String rootDir){ try { //1.构造观察类主要提供要观察的文件或目录,当然还有详细信息的filter FileAlterationObserver observer = new FileAlterationObserver( rootDir, FileFilterUtils.or(FileFilterUtils.fileFileFilter(), FileFilterUtils.directoryFileFilter()) ); //2.配置监听 observer.addListener(new FileListener( propertyConfig, variableConfig.getConfigDir())); //3.配置Monitor,第一个参数单位是毫秒,是监听的间隔;第二个参数就是绑定我们之前的观察对象 FileAlterationMonitor monitor = new FileAlterationMonitor(INTERVAL, observer); //开始监控 monitor.start(); } catch (Exception e) { log.error("FileListenerConfig.wrapSimple 监听失败,rootDir:{}", rootDir, e); } } }
4、编写properties配置类
import com.dingxianginc.channelaggregation.util.VariableConfig; import lombok.extern.slf4j.Slf4j; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.io.FileNotFoundException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * 配置文件组件 */ @Slf4j @Component public class PropertyConfig { private volatile MappropertyPathMap = new HashMap<>(); //扩展为caffeine缓存 避免hashMap产生运行时修改异常 private volatile Map propertyFieldMap = new HashMap<>(); @Autowired private VariableConfig variableConfig; @PostConstruct public void init(){ //根目录 loadPoint(variableConfig.getOverview()); } /** * 从path开始逐步加载所有 */ public PropertiesConfiguration loadPoint(String path){ try { //1.加载配置 PropertiesConfiguration config = PropertyUtil.loadProperty(path); if(config != null){ //2.遍历key集合 Iterator keys = config.getKeys(); while (keys.hasNext()){ String key = keys.next(); String[] fields = config.getStringArray(key);//默认按列表获取,默认英文逗号分隔, for(String field : fields){ if(StringUtils.isNotEmpty(field) && field.endsWith("properties")){ //4.递归实现 PropertiesConfiguration pointConfig = loadPoint(field); propertyFieldMap.put(field, pointConfig); } } } log.info("PropertyConfig.loadPoint path:{} 配置文件加载完毕", path); propertyPathMap.put(path, config); return config; } else { log.error("PropertyConfig.loadPoint path为空,请检查是否正确调用"); } } catch (ConfigurationException | FileNotFoundException e) { log.error("PropertyConfig.loadPoint 加载配置文件:{}失败", path, e); } return null; } /** * 设置caffeine缓存 * sync:设置如果缓存过期是不是只放一个请求去请求数据库,其他请求阻塞,默认是false * @return 优先返回缓存 * key的设置参见:https://blog.csdn.net/cr898839618/article/details/81109291 */ @Cacheable(value = "channel_agg" ,sync = true) public Map getPropertyPathMap(){ return propertyPathMap; } @Cacheable(value = "channel_agg", sync = true) public Map getPropertyFieldMap(){ return propertyFieldMap; } }
5、补充properties文件加载工具
import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.commons.lang3.StringUtils; import java.io.*; import java.net.URL; public class PropertyUtil { //加载文件的频率 单位:毫秒 private static final long RELOAD_PERIOD = 5000L; /** * getClassLoader().getResource方法就是在resources目录下查找 * 当传入值 path 以前缀/开头 则应使用class.getResource直接查询,否则使用class.getClassLoader().getResource进行查询 * @param path 文件路径 * @throws ConfigurationException Exception * @throws FileNotFoundException Exception */ public static PropertiesConfiguration loadProperty(String path) throws ConfigurationException, FileNotFoundException { //1.空判断 if(StringUtils.isEmpty(path)){ return null; } else { path = path.replaceAll("\\\\", "/");//以Linux路径为准 /** * 2.依据开头自主选择加载方法 * 第一:前面有 "/" 代表了工程的根目录,例如工程名叫做myproject,"/"代表了myproject * 第二:前面没有 "" 代表当前类的目录 */ URL url = path.startsWith("/") ? PropertyUtil.class.getResource(path) : PropertyUtil.class.getClassLoader().getResource(path); if(url == null){ throw new FileNotFoundException(path); } //3.加载配置文件 PropertiesConfiguration config = new PropertiesConfiguration(); //设置扫描文件的最小时间间隔 重新加载文件时会导致tomcat重启!!! /*FileChangedReloadingStrategy fileChangedReloadingStrategy = new FileChangedReloadingStrategy(); fileChangedReloadingStrategy.setRefreshDelay(RELOAD_PERIOD); config.setReloadingStrategy(fileChangedReloadingStrategy);*/ config.setAutoSave(true); //getResource和getResourceAsStream是有缓存的,这里重写文件流 config.load(new FileInputStream(url.getPath())); return config; } } }
6、properties文件配置类中引用了caffeine
依赖jar包:
org.springframework.boot spring-boot-starter-cache com.github.ben-manes.caffeine caffeine 2.7.0
7、简单配置:application.properties如下,Application启动类中启用缓存: @EnableCaching
spring.cache.type=caffeine spring.cache.cache-names=channel_agg spring.cache.caffeine.spec=initialCapacity=100,maximumSize=5000,expireAfterAccess=10s
8、caffeine配置参数详解
注解在Spring中的应用很广泛,几乎成为了其标志,这里说下使用注解来集成缓存。
caffeine cache方面的注解主要有以下5个
@Cacheable 触发缓存入口(这里一般放在创建和获取的方法上)
@CacheEvict 触发缓存的eviction(用于删除的方法上)
@CachePut 更新缓存且不影响方法执行(用于修改的方法上,该注解下的方法始终会被执行)
@Caching 将多个缓存组合在一个方法上(该注解可以允许一个方法同时设置多个注解)
@CacheConfig 在类级别设置一些缓存相关的共同配置(与其它缓存配合使用)
可参见:caffeine 通过spring注解方式引入
代码版简版如下:
import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.cache.CacheManager; import org.springframework.cache.caffeine.CaffeineCacheManager; import java.util.Collections; import java.util.concurrent.TimeUnit; /* @Slf4j @Configuration @deprecated 使用配置方式 此方式留待扩展 配置方式测试见 @see ChannelAggregationApplicationTests.testCache */ @Deprecated @SuppressWarnings("all") public class CacheConfig { private static final String CACHE_NAME = "channel_agg"; //配置CacheManager /*@Bean(name = "caffeine")*/ public CacheManager cacheManagerWithCaffeine() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); /** * initialCapacity=[integer]: 初始的缓存空间大小 * maximumSize=[long]: 缓存的最大条数 * maximumWeight=[long]: 缓存的最大权重 * expireAfterAccess=[duration秒]: 最后一次写入或访问后经过固定时间过期 * expireAfterWrite=[duration秒]: 最后一次写入后经过固定时间过期 * refreshAfterWrite=[duration秒]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存 * weakKeys: 打开key的弱引用 * weakValues:打开value的弱引用 * softValues:打开value的软引用 * recordStats:开发统计功能 */ Caffeine caffeine = Caffeine.newBuilder() //cache的初始容量值 .initialCapacity(100) //maximumSize用来控制cache的最大缓存数量,maximumSize和maximumWeight(最大权重)不可以同时使用, .maximumSize(5000) //最后一次写入或者访问后过久过期 .expireAfterAccess(2, TimeUnit.HOURS) //创建或更新之后多久刷新,指定了refreshAfterWrite还需要设置cacheLoader .refreshAfterWrite(10, TimeUnit.SECONDS); cacheManager.setCaffeine(caffeine); cacheManager.setCacheLoader(cacheLoader()); cacheManager.setCacheNames(Collections.singletonList(CACHE_NAME));//根据名字可以创建多个cache,但是多个cache使用相同的策略 cacheManager.setAllowNullValues(false);//是否允许值为空 return cacheManager; } /** * 必须要指定这个Bean,refreshAfterWrite配置属性才生效 */ /*@Bean*/ public CacheLoader
上述就是小编为大家分享的springboot2 中怎么动态加载properties 文件了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注创新互联行业资讯频道。