這期內容當中小編將會給大家帶來有關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 文件了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。