动态配置读取

308 阅读3分钟

一、前言

说明: 我们如果要从properties配置文件中获取相应的值,一般通过@Value或者其他注解来做,但是我们如果在使用 Spring的配置文件中修改完,需要重启服务才能够生效;但是我们希望如果我们使用动态配置,比如从Nacos 或者 SpringConfig 中来获取配置文件,当配置中心进行了改变,需要我们这边立即生效!

二、具体实现

2.1 属性文件读取工具类:PropertiesUtils

package com.ssm.tool;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileInputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

@Component
@Slf4j
public class PropertiesUtils {

    /**
     * 属性文件 与 属性集合的映射
     */
    private Map<String, Properties> propertiesMap = new HashMap<>();

    /**
     * 属性文件 与 修改时间的映射
     */
    private Map<String, Long> modifyTimeMap = new HashMap<>();

    /**
     * 指定的属性文件的目录路径
     */
    private String configPath = "";

    public void setConfigPath(String path) {
        this.configPath = path;
    }

    /**
     * 单例模式 构造PropertiesUtils
     */
    private PropertiesUtils() {} //私有化,禁止外部空参构造
    
    public static class SingleHolder {
        private static PropertiesUtils instance = new PropertiesUtils();
    }

    public static PropertiesUtils getInstance() {
        return SingleHolder.instance;
    }

    /**
     * 获取属性文件中key对应的值
     * @param propertyFileName 属性文件名称
     * @param key 属性文件中属性的key
     */
    public String getPropertyValue(String propertyFileName, String key) {
        try {
            String fileName = covertFileName(propertyFileName);
            //如果map中还没有该属性文件,就加载进去
            if(propertiesMap.get(fileName) == null) {
                loadProperties(fileName);
            } else {
                //如果存在,就检查属性文件是否被修改
                checkPropertiesModified(fileName);
            }
            return propertiesMap.get(fileName).getProperty(key); //Properties本身也是属性集合
        } catch (Exception e) {
            log.error("PropertiesUtils.getPropertyValue.error:{}", e.getMessage(), e);
            return "";
        }
    }


    /**
     * 校验属性文件的名称(.properties结尾的,去掉后缀再返回)
     */
    private String covertFileName(String propertyFileName) {
        //不以.properties结尾的,直接返回
        if(!propertyFileName.endsWith(".properties")) {
            return propertyFileName;
        }
        //去掉后缀,返回
        int index = propertyFileName.indexOf(".");
        return propertyFileName.substring(0, index);
    }

    /**
     * 加载属性文件到propertiesMap集合中去
     */
    private void loadProperties(String fileName) throws URISyntaxException {
        //根据配置文件的名称,来获取相应配置文件
       File file = getPropertyFile(fileName);
       //获取文件最后一次的修改时间
        long newTime = file.lastModified();

        //二次判断--文件已存在属性文件集合时,把旧属性文件删除(判断文件是否被修改时也要使用该方法)
        if (propertiesMap.get(fileName) != null) {
            propertiesMap.remove(fileName);
        }

        Properties properties = new Properties();
        try {
            //从指定的文件中加载属性列表(键值对)到Properties对象中
            properties.load(new FileInputStream(file));
        } catch (Exception e) {
            log.error("PropertiesUtils.loadProperties.error:{}", e.getMessage(), e);
        }

        propertiesMap.put(fileName, properties);
        modifyTimeMap.put(fileName, newTime);
    }

    /**
     * 根据文件名称获取文件对象
     */
    private File getPropertyFile(String fileName) throws URISyntaxException {
        File propertiesFile = null;

        //1.如果 指定的属性文件的目录路径configPath不为空(提供了统一的属性文件存放目录),则直接拼接
        if(!StringUtils.isBlank(configPath)) {
            return new File(this.configPath + File.separator + fileName + ".properties"); //File.separator == .
        }

        //2.如果configPath为空,那么就使用内置的user.dir路径(是一个系统属性,表示当前JVM的“用户工作目录”的绝对路径。这通常是启动Java应用程序的目录)
        String dir = System.getProperty("user.dir") + File.separator + fileName + ".properties";
        propertiesFile = new File(dir);

        //3. 如果内置路径没有文件,那么就从最终指定路径中查找
        if(!propertiesFile.exists()) {
            // 同级(编译路径)的resource根目录下,是否存在对应文件,存在则返回该地址
            URL url = PropertiesUtils.class.getResource("/" + fileName + ".properties");
            if(url != null) {
                propertiesFile = new File(url.getFile());
            }
        }
        return propertiesFile;
    }

    /**
     * 检查文件是否被修改
     */
    private void checkPropertiesModified(String fileName) throws URISyntaxException {
        //拿到file文件
        File file = getPropertyFile(fileName);
        //拿到文件最后一次修改时间
        long newTime = file.lastModified();

        //拿到该文件上次加载时的最后一次修改时间
        Long lastModifiedTime = modifyTimeMap.get(fileName);

        if(newTime == 0) { //文件不存在
            if(lastModifiedTime == null) {
                log.error(fileName + ".properties file does not exist!");
            }
        } else if(newTime > lastModifiedTime) {
            //文件更新,重新加载
            loadProperties(fileName);
        }
    }
}

2.2 测试类

package com.ssm.user;

import com.ssm.tool.PropertiesUtils;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = UserApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Slf4j
public class PropertiesTest {

    @Test
    public void testProperties() {
        String testValue = PropertiesUtils.getInstance().getPropertyValue("test", "ape.name");
        System.out.println(testValue);
    }
}

2.3 相关配置

说明: 我们使用getPropertyFile方法第三种获取文件的方式,需要在调用类的同级(编译路径)的resource根目录下,是否存在对应文件,存在则返回该地址;那么我们在test中写的,其根目录也在test中,创建一个test.properties文件即可(平常就使用main目录下默认的resource)!

image.png

2.4 运行结果

image.png