一、背景
我们都知道spring可以帮我们读取properties配置文件,我们只需要简单配置一行:
<context:property-placeholder location="classpath:properties/*.properties" file-encoding="UTF-8"/>
就能在java代码中使用@Value("${xxxx}")美滋滋的读取配置文件,但是如果有需要在java代码中使用类似PropertiesUtil.getProperties(“xxxx”)的方式去读取配置,有几种实现方式呢?
1、最笨的办法,直接读取指定文件名的配置,很明显太死板,不灵活,不可取
2、依靠spring的properties解析类自定义PropertiesUtil
二、本文主要讲第二种方式的实现哈,注意!在spring3.1之前是使用PropertyPlaceholderConfigurer,而spring3.1版本之后加载properties配置的类叫PropertySourcesPlaceholderConfigurer,如果你的项目还是使用的老的版本或老的配置方式,可以参考这篇文章的配置方式:spring3.1之前的配置方式
三、接下来让我们看看在spring3.1+版本中,重写PropertySourcesPlaceholderConfigurer类来创建PropertiesUtil的实现方式
1、spring3.1之前是通过新建一个类并继承PropertyPlaceholderConfigurer类再重写processProperties方法来实现的,但在PropertySourcesPlaceholderConfigurer中这个方法已经被弃用,所以直接拷贝老的实现换个继承的类来实现是不可取的
2、通过阅读PropertySourcesPlaceholderConfigurer类的源码不难发现,它加载配置文件的核心方法是postProcessBeanFactory(),通过debug可以进一步了解到读取的配置都在该类的appliedPropertySources中,实现思路就有了!我们新建一个类继承PropertySourcesPlaceholderConfigurer并重写它的postProcessBeanFactory()方法,将配置都加载到一个map中,然后提供一个PropertiesUtil的类来从这个map种读取配置就好了,上才艺:
public class PropertiesUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(PropertiesUtil.class);
private static Map<String, Object> propertiesMap;
public static String getProperties(String name) {
Object value = propertiesMap.get(name);
return value != null ? String.valueOf(value) : null;
}
public static void initPropertiesMap(PropertySourcesPlaceholderConfigurer propertyConfigurer) {
if (null == propertiesMap) {
propertiesMap = new HashMap<>();
}
// 装载远程配置,笔者项目中使用nacos作为配置中心,没有的可以忽略或去掉这段代码
PropertySources appliedPropertySources = propertyConfigurer.getAppliedPropertySources();
PropertySource<?> environmentPropertySource = appliedPropertySources
.get(PropertySourcesPlaceholderConfigurer.ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME);
StandardEnvironment environment = (StandardEnvironment) environmentPropertySource.getSource();
MutablePropertySources mutablePropertySources = environment.getPropertySources();
mutablePropertySources.forEach(propertySource -> {
if (propertySource.getSource() instanceof Map) {
propertiesMap.putAll((Map<? extends String, ?>) propertySource.getSource());
}
});
// 装载本地配置
PropertiesPropertySource localPropertySource = (PropertiesPropertySource) appliedPropertySources
.get(PropertySourcesPlaceholderConfigurer.LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME);
if (localPropertySource != null) {
propertiesMap.putAll(localPropertySource.getSource());
}
}
public static void reloadConfig(String config) {
if (StrUtil.isBlank(config)) {
return;
}
Properties properties = new Properties();
try {
properties.load(new StringReader(config));
} catch (IOException e) {
LOGGER.error("load config error. ", e);
return;
}
Set<Map.Entry<Object, Object>> entries = properties.entrySet();
entries.forEach(i -> {
propertiesMap.put(String.valueOf(i.getKey()), i.getValue());
LOGGER.debug("key={}, value={} updated", i.getKey(), i.getValue());
});
}
}
上面的工具类封装了初始化map及刷新配置的方法,刷新配置用于从配置中心拉取到更新,同步更新map中的属性,实现实时更新配置,定义PropertySourcesPlaceholderConfigurer的实现类:
public class PropertyConfigurer extends PropertySourcesPlaceholderConfigurer {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
super.postProcessBeanFactory(beanFactory);
PropertiesUtil.initPropertiesMap(this);
}
}
四、最后就是要在spring的配置文件中使用自定义的类来加载配置了!
<bean class="com.lieni.core.web.config.PropertyConfigurer">
<property name="locations">
<list>
<value>classpath:properties/*.properties</value>
<value>classpath:properties/*/*.properties</value>
</list>
</property>
<property name="fileEncoding" value="UTF-8" />
</bean>
五、java代码中使用
String value = PropertiesUtil.getProperties("key");