允许我们使用属性文件(.properties)的形式对bean的属性进行替换。下面是一个简单的demo:
定义如下的属性文件(property.properties):
student.name=dog
格式为: bean名字.属性名字=值。由如下的bean:
<bean id="student" class="base.Student">
<property name="name" value="skywalker" />
<property name="age" value="30" />
</bean>
进行如下的配置:
<context:property-override location="property.properties" />
运行如下的代码:
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
SimpleBean bean = SimpleBean.class.cast(context.getBean(SimpleBean.class));
System.out.println(bean.getStudent().getName());
context.close();
}
打印的便是dog,而不是skywalker。
具体的实现类是PropertyOverrideBeanDefinitionParser,其类图如下:
解析的原理是将此配置相关的信息保存到BeanDefinition中,更准确的说是一个GenericBeanDefinition。解析的源码:
AbstractPropertyLoadingBeanDefinitionParser.doParse:
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String location = element.getAttribute("location");
if (StringUtils.hasLength(location)) {
String[] locations = StringUtils.commaDelimitedListToStringArray(location);
builder.addPropertyValue("locations", locations);
}
String propertiesRef = element.getAttribute("properties-ref");
if (StringUtils.hasLength(propertiesRef)) {
builder.addPropertyReference("properties", propertiesRef);
}
String fileEncoding = element.getAttribute("file-encoding");
if (StringUtils.hasLength(fileEncoding)) {
builder.addPropertyValue("fileEncoding", fileEncoding);
}
String order = element.getAttribute("order");
if (StringUtils.hasLength(order)) {
builder.addPropertyValue("order", Integer.valueOf(order));
}
builder.addPropertyValue("ignoreResourceNotFound",
Boolean.valueOf(element.getAttribute("ignore-resource-not-found")));
builder.addPropertyValue("localOverride",
Boolean.valueOf(element.getAttribute("local-override")));
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
}
此属性允许我们直接引用一个java.util.Properties类型的bean作为数据源,示例:
<context:property-override properties-ref="property" />
<bean id="property" class="java.util.Properties">
<constructor-arg>
<props>
<prop key="student.name">cat</prop>
</props>
</constructor-arg>
</bean>
这样便可以看到结果。
此属性用以指定其优先级,假设配置了多个context:property-override并且里面有相同的字段,那么将依赖order决定结果。
如果设为true,那么对于没有找到的属性文件将会忽略,否则会抛出异常,默认为false,抛异常。
如果设为true,那么对于没有找到对应的key将会忽略,否则抛出异常,默认false。
这个属性让我很迷惑。Spring说是此选项决定"local"的属性是否可以覆盖属性文件中的值。正如下面说的,实际上属性文件被解析到了PropertyOverrideConfigurer对象,其父类PropertiesLoaderSupport有一个字段:
protected Properties[] localProperties;
/**
* Set local properties, e.g. via the "props" tag in XML bean definitions.
* These can be considered defaults, to be overridden by properties
* loaded from files.
*/
public void setProperties(Properties properties) {
this.localProperties = new Properties[] {properties};
}
可以看出,这应该就是Spring所说的"local"属性。好,我们来注入一下:
<context:property-override location="property.properties" local-override="false" />
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="properties">
<array>
<props>
<prop key="student.name">cat</prop>
</props>
</array>
</property>
</bean>
然而Spring在注册PropertyOverrideConfigurer的时候根本没有检查容器中是否已经有此类型的BeanDefinition存在,这就导致容器中会同时存在两个!在此种情况下local-override根本没什么卵用,因为后面的PropertyOverrideConfigurer始终会覆盖前一个,local-override是针对一个PropertyOverrideConfigurer来说的,那么问题来了,除此之外如何通过XML向"local"注入?(context:property-override不允许子标签存在)
保存的BeanDefinition的beanClass为PropertyOverrideConfigurer,其类图:
入口当然是BeanFactoryPostProcessor.postProcessBeanFactory(PropertyResourceConfigurer):
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
try {
// 属性加载
Properties mergedProps = mergeProperties();
// Convert the merged properties, if necessary.
convertProperties(mergedProps);
// Let the subclass process the properties.
processProperties(beanFactory, mergedProps);
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
PropertiesLoaderSupport.mergeProperties:
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) {
CollectionUtils.mergePropertiesIntoMap(localProp, result);
}
}
if (!this.localOverride) {
// Load properties from file afterwards, to let those properties override.
loadProperties(result);
}
return result;
}
可以看出,对local-override的支持是通过改变local和文件两者的加载顺序来实现的。
convertProperties是个空实现,因为这里并不需要,在bean实际生成的时候才会转换。
就是逐个属性调用PropertyOverrideConfigurer.applyPropertyValue:
protected void applyPropertyValue(
ConfigurableListableBeanFactory factory, String beanName, String property, String value) {
BeanDefinition bd = factory.getBeanDefinition(beanName);
while (bd.getOriginatingBeanDefinition() != null) {
bd = bd.getOriginatingBeanDefinition();
}
PropertyValue pv = new PropertyValue(property, value);
pv.setOptional(this.ignoreInvalidKeys);
bd.getPropertyValues().addPropertyValue(pv);
}
addPropertyValue会遍历PropertyValue链表,找到name相同的进行value替换。