系统中访问properties文件中定义的配置项是很常见的需求,jdk提供java.util.Properties类加载指定的配置文件(当然不一定从本地文件、任意来源的流对象也可以)。
在一个大型系统中,我们有可能需要定义多个配置文件,如果每个文件都需要声明一个Properties与之对应,那么代码会显得很啰嗦。
方法一:统一写一个加载工具类,可以,但是不见得最好。
方法二:借助spring framework中的Envioment对象和@PropertySource属性
以下是一段实例代码,定义一个ApplicationConfig类,内部封装了 envioment对象,然后通过envioment获取属性。
@Component
public class ApplicationConfig {
@Autowired(required = false)
private Environment environment;
/**
* 获取指定key对应的string值,key不存在会抛出异常
*
* @param key
* @return
*/
public String getString(String key) {
String val = internalGet(key);
if (val == null) {
throw new RuntimeException("key:" + key + " not exist!");
}
return val;
}
/**
* 获取指定key对应的string值,如果key不存在则返回defaultVal
*
* @param key
* @param defaultVal
* @return
*/
public String getString(String key, String defaultVal) {
String val = internalGet(key);
if (val == null) {
return defaultVal;
} else {
return val;
}
}
protected String internalGet(String key) {
return this.environment != null ? this.environment.getProperty(key)
: System.getProperty(key);
}
除了getString,getInteger、getBoolean也可以由这个类统一处理,方便外部调用。
如果environment被正确注入,那么我们就可以统一从中获取“所有属性”。
什么是"所有属性"?
很多,说些常见的:
- 系统启动时参数,即 -Dkey=value 方式定义的;
- 系统属性,即System.getProperty(String key);
- 环境变量
- 容器内置的一些属性,比如springboot启动时,应用上文参数、application.properties、bootstrap.properties等,这些特定的容器也会把自身的一些属性加入到environment中
- spting cloud config(分布式配置管理框架)的客户端会将服务器抓取的配置放到environment中
- 本地属性文件,当然需要通过某种方式加载进去(别急,下文会说)
当然,还有很多属性来源,不一一列举了。
我们的重点是,有了environment,我们可以通过它读取所有需要加载的属性文件了,算是一个统一的读取入口。
那么如何方便加载配置文件呢?那就要借助@PropertySource注解了。
@Component
@PropertySource(value = "classpath:myconfig.properties", ignoreResourceNotFound = true)
public class MyConfig {
@Value("${key1:value1}")
private String key1;
@Value("${key2:value2}")
private String key2;
public String getKey1() {
return key1;
}
public String getKey2() {
return key2;
}
}
我们定义一个Bean:MyConfig(我们可以通过很多方式定义bean,但不管什么方式,一定要确保MyConfig可以被springIOC加载),由于它被添加了@PropertySource注解,指定了属性文件的位置,那么spring就会尝试加载这个文件,并最终添加到environment中。
ignoreResourceNotFound属性意思是,如果文件不存在,就忽略,否则会抛异常,所以如果这个文件不是必须的,就设置成true.
本人也建议设置true,按照约定大于配置的原则,我们可以在代码段处理默认值。
那么如何处理默认值呢?
一,如果通过envioment获取,可以调用String getString(String key, String defaultVal)方法,外部传入默认值;
二,通过@Value注解
@Value("${key1:value1}")意思是,spring会从environment中获取key1的值,添加到key1字段中。
需要注意的是:只要environment中存在key1即可,也就是说,key1可以通过其他方式添加,不一定需要定义在myconfig.properties中;另外,所有environment的属性就可以用这样的方式注入到任何bean的字段中。
还有一点,如果environment中不存在key1,那么麻烦了,就会出错,所以key1:后面的value1就是默认值,通过这样的方式避免出错,也定义了默认值。
加载PropertySource的spring源代码在org.springframework.context.annotation.ConfigurationClassParser类中,我贴一下代码
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
String[] locations = propertySource.getStringArray("value");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
for (String location : locations) {
try {
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation);
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
// Placeholders not resolvable or resource not found when trying to open it
if (ignoreResourceNotFound) {
if (logger.isInfoEnabled()) {
logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
}
}
else {
throw ex;
}
}
}
}
大家可以明显看到,ignoreResourceNotFound属性就在catch代码块里起作用了。
最后说明下优先级:
系统启动参数>application.properties参数(如果是springboot程序)>自定义文件参数>默认值。
以下是调试代码的截图
大家可以看到 myconfig文件对应的properysource在第9个,系统参数在第5个,而springboot的application.properties定义的参数是第7个,它们优先级都比myconfig要高。
以上就是pieces框架如何加载属性文件和访问属性的介绍,如果配置项是从springcloudconfig服务端获取的话,那么就不适合用@PropertySource的方式了,这个以后有机会再详细说明。