Spring 是如何加载 *.properties 文件的?
1、属性加载器
PropertyPlaceholderConfigurer.java 类图结构
通过类图,我们可以看到,PropertyPlaceholderConfigurer 实现了 BeanFactoryPostProcessor 接口,该接口中只有一个接口方法,通过方法注释我么可以了解到:
(1)、该接口可在 bean 实例化之前修改 Application Context ;
(2)、可以加载所有的bean定义,但是bean还没有并实例化;
(3)、允许通过对期待实例化的 Bean 属性进行覆盖和添加。
(4)、BeanFactoryPostProcessor 是一个特别的接口,当Spring加载任何实现了这个接口的bean的配置时,都会在工厂载入所有bean的配置之后执行 postProcessBeanFactory 方法。
public interface BeanFactoryPostProcessor {
/**
* Modify the application context's internal bean factory after its standard
* initialization. All bean definitions will have been loaded, but no beans
* will have been instantiated yet. This allows for overriding or adding
* properties even to eager-initializing beans.
* @param beanFactory the bean factory used by the application context
* @throws org.springframework.beans.BeansException in case of errors
*/
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
2、属性资源加载器
(1)、PropertyResourceConfigurer # postProcessBeanFactory
/**
* {@linkplain #mergeProperties Merge}, {@linkplain #convertProperties convert} and
* {@linkplain #processProperties process} properties against the given bean factory.
* @throws BeanInitializationException if any properties cannot be loaded
*/
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
Properties mergedProps = mergeProperties();//加载配置文件到properties
// Convert the merged properties, if necessary.
convertProperties(mergedProps); //必要则转换合并后的属性
// Let the subclass process the properties.
processProperties(beanFactory, mergedProps);// 子类初始这些属性,例如:PropertyPlaceholderConfigurer
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
(2)、PropertiesLoaderSupport # mergeProperties 完成属性文件的读取。
通过源代码分析我们可以看出来,如果 property 中的属性key存在重复的清苦,则后加载的会覆盖先加载的key值!!!
/**
* Return a merged Properties instance containing both the
* loaded properties and properties set on this FactoryBean.
*/
protected Properties mergeProperties() throws IOException {
Properties result = new Properties();
if (this.localOverride) {
// Load properties from file upfront, to let local properties override.
loadProperties(result);
}
if (this.localProperties != null) {
for (Properties localProp : this.localProperties) {// 多个属性文件循环读取,key 相同则覆盖
CollectionUtils.mergePropertiesIntoMap(localProp, result);
}
}
if (!this.localOverride) {
// Load properties from file afterwards, to let those properties override.
loadProperties(result);
}
return result;
}
/**
* Load properties into the given instance.
* @param props the Properties instance to load into
* @throws IOException in case of I/O errors
* @see #setLocations
*/
protected void loadProperties(Properties props) throws IOException {
if (this.locations != null) {
for (Resource location : this.locations) {
if (logger.isInfoEnabled()) {
logger.info("Loading properties file from " + location);
}
try {
PropertiesLoaderUtils.fillProperties(
props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);// 将配置文件中的属性读入到 props 中
}
catch (IOException ex) {
if (this.ignoreResourceNotFound) {
if (logger.isWarnEnabled()) {
logger.warn("Could not load properties from " + location + ": " + ex.getMessage());
}
}
else {
throw ex;
}
}
}
}
}
3、属性读取工具类
PropertiesLoaderUtils # fillProperties 通过IO读取资源文件。
4、AbstractBeanFactory 解析占位符
AbstractBeanFactory # resolveEmbeddedValue
public String resolveEmbeddedValue(String value) {
String result = value;
for (StringValueResolver resolver : this.embeddedValueResolvers) {
if (result == null) {
return null;
}
result = resolver.resolveStringValue(result);
}
return result;
}
5、DefaultListableBeanFactory # resolveDependency 方法
6、AutowiredAnnotationBeanPostProcessor 属性自动注入
通过反射的方式将占位符的值得以替换。
以上即是整个 properties 文件从加载到替换属性的全过程。