一、前言
说明: 我们如果要从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)!