这期内容当中小编将会给大家带来有关springboot2 中怎么动态加载properties 文件,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。
1、比较好的方案,采用文件监控 依赖 commons-io2
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
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 Map<String, PropertiesConfiguration> propertyPathMap = new HashMap<>();
//扩展为caffeine缓存 避免hashMap产生运行时修改异常
private volatile Map<String, PropertiesConfiguration> 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<String> 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<String, PropertiesConfiguration> getPropertyPathMap(){
return propertyPathMap;
}
@Cacheable(value = "channel_agg", sync = true)
public Map<String, PropertiesConfiguration> 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包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- caffeine -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.7.0</version>
</dependency>
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<Object, Object> cacheLoader() {
return new CacheLoader<Object, Object>() {
@Override
public Object load(Object key) throws Exception { return null;}
// 重写这个方法将oldValue值返回回去,进而刷新缓存
@Override
public Object reload(Object key, Object oldValue) throws Exception {
return oldValue;
}
};
}
}
上述就是小编为大家分享的springboot2 中怎么动态加载properties 文件了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注亿速云行业资讯频道。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://my.oschina.net/ysma1987/blog/3081622