千家信息网

springboot2 中怎么动态加载properties 文件

发表于:2025-02-02 作者:千家信息网编辑
千家信息网最后更新 2025年02月02日,这期内容当中小编将会给大家带来有关springboot2 中怎么动态加载properties 文件,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。1、比较好的方案,采
千家信息网最后更新 2025年02月02日springboot2 中怎么动态加载properties 文件

这期内容当中小编将会给大家带来有关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@Componentpublic 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@Configurationpublic 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@Componentpublic class PropertyConfig {    private volatile Map propertyPathMap = 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=caffeinespring.cache.cache-names=channel_aggspring.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 cacheLoader() {        return new CacheLoader() {            @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 文件了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注行业资讯频道。

0