一、spring 中读取properties 配置文件的几种方式
1.使用<context:property-placeholder > 标签
<context:property-placeholder location="classpath:properties/ee.properties" />
2.创建 PropertySourcesPlaceholderConfigurer bean
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:properties/ee.properties</value>
</list>
</property>
</bean>
3.创建properties bean
<util:properties id="util_Spring" local-override="false" location="properties/bb.properties"/>
4.使用 PropertiesFactoryBean
<bean id="propertiesFactoryBean" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="locations" value="classpath:properties/cc.properties"/>
</bean>
<bean class="java.util.Properties" factory-bean="propertiesFactoryBean"/>
使用 property-placeholder 标签实际使用的是PropertySourcesPlaceholderConfigurer 解析properties 文件
二、property-placeholder 读取properties 文件源码分析
- property-placeholder 标签解析
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
//注册property-placeholder 解析器
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}
- PropertyPlaceholderBeanDefinitionParser 源码
@Override
protected Class<?> getBeanClass(Element element) {
// As of Spring 3.1, the default value of system-properties-mode has changed from
// 'FALLBACK' to 'ENVIRONMENT'. This latter value indicates that resolution of
// placeholders against system properties is a function of the Environment and
// its current set of PropertySources.
if (SYSTEM_PROPERTIES_MODE_DEFAULT.equals(element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE))) {
//解析 成 PropertySourcesPlaceholderConfigurer 类
return PropertySourcesPlaceholderConfigurer.class;
}
// The user has explicitly specified a value for system-properties-mode: revert to
// PropertyPlaceholderConfigurer to ensure backward compatibility with 3.0 and earlier.
return PropertyPlaceholderConfigurer.class;
}
- PropertySourcesPlaceholderConfigurer 源码
PropertySourcesPlaceholderConfigurer 继承 PropertyResourceConfigurer 实现了 BeanFactoryPostProcessor 接口,因此可以在类解析完成尚未实例化之前通过postProcessBeanFactory 方法修改类定义.
看如下源码:
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (this.propertySources == null) {
this.propertySources = new MutablePropertySources();
if (this.environment != null) {
this.propertySources.addLast(
new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
@Override
public String getProperty(String key) {
return this.source.getProperty(key);
}
}
);
}
try {
PropertySource<?> localPropertySource =
new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
if (this.localOverride) {//如果允许覆盖已经加载properties文件,设置最高优先级
this.propertySources.addFirst(localPropertySource);
}
else {
this.propertySources.addLast(localPropertySource);
}
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
// 处理properties
processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
this.appliedPropertySources = this.propertySources;
}
processProperties 方法
/** 访问bean factory 中的每个bean 定义,使用properties 中的值替换 ${...}
* Visit each bean definition in the given bean factory and attempt to replace ${...} property
* placeholders with values from the given properties.
*/
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
final ConfigurablePropertyResolver propertyResolver) throws BeansException {
propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
propertyResolver.setValueSeparator(this.valueSeparator);
StringValueResolver valueResolver = new StringValueResolver() {
@Override
public String resolveStringValue(String strVal) {
String resolved = (ignoreUnresolvablePlaceholders ?
propertyResolver.resolvePlaceholders(strVal) :
propertyResolver.resolveRequiredPlaceholders(strVal));
if (trimValues) {//去除左右两边的空格
resolved = resolved.trim();
}
return (resolved.equals(nullValue) ? null : resolved);
}
};
//真正处理填充占位符中的值
doProcessProperties(beanFactoryToProcess, valueResolver);
}
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
//读取所有定义的bean 名字
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
for (String curName : beanNames) {
// Check that we're not parsing our own bean definition,
// to avoid failing on unresolvable placeholders in properties file locations.
if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
// 填充占位符内容
visitor.visitBeanDefinition(bd);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
}
}
}
// New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
beanFactoryToProcess.resolveAliases(valueResolver);
// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
三、注意点
PropertySourcesPlaceholderConfigurer 在解析bean 定义填充占位符中的内容时,会遍历bean factory 中的所有bean 定义,如果有些bean 中引用的key 所在properties配置文件不是由当前PropertySourcesPlaceholderConfigurer 解析,会出现以下异常:
Could not resolve placeholder 'xxx' in value "${xxx}"