Spring技术内幕5——利用property-placeholder节点配置Bean属性源码解析
我们在基于spring开发应用的时候,一般都会将数据库的配置放置在properties文件中.便于后期的应用修改及维护,甚至我们可以将一些通用的配置也放在properties中进行相关的初始化工作。
我们看一个常用的例子:
<context:property-placeholder location="classpath:config/resource/config.properties" />
config.properties的配置如下:
connectTimeout = 3000
readTimeout = 15000
我们配置一个SystemConfig的类:
@Component
public class SystemConfig {
// 超时时间配置
@Value("${connectTimeout}")
private int connectTimeout;
@Value("${readTimeout}")
private int readTimeout;
这样我们在使用的时候直接就可以获取到我们配置的相关初始值。
这个操作是我们常用的操作
现在有这样的一个问题,如果我们有两个配置文件config1.properties和config.properties文件 里面有一些相同的配置项,会采用哪个配置文件中的值?有什么规律吗?我们来看
config1.properties中:
connectTimeout = 3001
readTimeout = 15001
config.properties的配置如下:
connectTimeout = 3000
readTimeout = 15000
那么程序在启动之后具体的值到底该选用哪个呢?
场景1:
xml配置如下:
<context:property-placeholder location="classpath:config/resource/config.properties,classpath:config/resource/config1.properties" />
这里我们配置的location为两个,config在前,config1在后
结果:
我们通过Systemconfig获取connectTimeout的时候,获取到的值是3001,就是说后面的config1.properties的值将前面的值覆盖了。
场景2:
xml配置如下:
<context:property-placeholder
location="classpath:config/resource/config.properties"
ignore-unresolvable="true"/>
<context:property-placeholder
location="classpath:config/resource/config1.properties"
ignore-unresolvable="true"/>
这里我们配置了两个property-placeholder,
结果:
获取SystemConfig的connectTimeout 值为3000,可以看见后面的config1.properties并没有将前面的properties覆盖。
场景1和场景2结果截然不同的原因如下:
我们首先分析场景1:
property-placeholder其实就是使用了PropertySourcesPlaceholderConfigurer这个类,我们分析PropertySourcesPlaceholderConfigurer这个类,我们来看下这个类的继承关系:
我们可以看到这个类继承了BeanFactoryPostProcesser这个接口,我们仔细看这个接口的定义:
/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.config;
import org.springframework.beans.BeansException;
/**
* Allows for custom modification of an application context's bean definitions,
* adapting the bean property values of the context's underlying bean factory.
*
* <p>Application contexts can auto-detect BeanFactoryPostProcessor beans in
* their bean definitions and apply them before any other beans get created.
*
* <p>Useful for custom config files targeted at system administrators that
* override bean properties configured in the application context.
*
* <p>See PropertyResourceConfigurer and its concrete implementations
* for out-of-the-box solutions that address such configuration needs.
*
* <p>A BeanFactoryPostProcessor may interact with and modify bean
* definitions, but never bean instances. Doing so may cause premature bean
* instantiation, violating the container and causing unintended side-effects.
* If bean instance interaction is required, consider implementing
* {@link BeanPostProcessor} instead.
*
* @author Juergen Hoeller
* @since 06.07.2003
* @see BeanPostProcessor
* @see PropertyResourceConfigurer
*/
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;
}
这里特别告知了我们调用postProcessBeanFactory方法的时候bean definitions将被加载但是所有的Bean还没有实例化,因此我们可以在这里对Bean的实例化做一些修改(This allows for overriding or adding properties even to eager-initializing beans)
因此我们继续来看:
PropertySourcesPlaceholderConfigurer的postProcessBeanFactory方法实现:
/**
* {@inheritDoc}
* <p>Processing occurs by replacing ${...} placeholders in bean definitions by resolving each
* against this configurer's set of {@link PropertySources}, which includes:
* <ul>
* <li>all {@linkplain org.springframework.core.env.ConfigurableEnvironment#getPropertySources
* environment property sources}, if an {@code Environment} {@linkplain #setEnvironment is present}
* <li>{@linkplain #mergeProperties merged local properties}, if {@linkplain #setLocation any}
* {@linkplain #setLocations have} {@linkplain #setProperties been}
* {@linkplain #setPropertiesArray specified}
* <li>any property sources set by calling {@link #setPropertySources}
* </ul>
* <p>If {@link #setPropertySources} is called, <strong>environment and local properties will be
* ignored</strong>. This method is designed to give the user fine-grained control over property
* sources, and once set, the configurer makes no assumptions about adding additional sources.
*/
@Override
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) {
this.propertySources.addFirst(localPropertySource);
}
else {
this.propertySources.addLast(localPropertySource);
}
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
}
这里我们继续看:
PropertySource<?> localPropertySource =
new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
这个里面的mergeProperties方法 PropertiesLoaderSupport中的mergeProperties方法如下:
/**
* 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) {
CollectionUtils.mergePropertiesIntoMap(localProp, result);
}
}
if (!this.localOverride) {
// Load properties from file afterwards, to let those properties override.
loadProperties(result);
}
return result;
}
这里便是开始loadProperties的地方:
/**
* 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);
}
catch (IOException ex) {
if (this.ignoreResourceNotFound) {
if (logger.isWarnEnabled()) {
logger.warn("Could not load properties from " + location + ": " + ex.getMessage());
}
}
else {
throw ex;
}
}
}
}
}
由于我们有两个locations所以这里做循环的时候后面配置的config1.properties会将之前的属性值覆盖掉。
场景2的分析:
场景2在这里和场景1之间完全不一样,场景2配置了两个property-placeholder属性。也就是说相当于有两个PropertySourcesPlaceholderConfigurer,由于config.properties文件配置的PropertySourcesPlaceholderConfigurer在前,因此config.properties的PropertySourcesPlaceholderConfigurer首先加载。
我们继续往下看:
PropertySourcesPlaceholderConfigurer的postProcessBeanFactory方法:
/**
* {@inheritDoc}
* <p>Processing occurs by replacing ${...} placeholders in bean definitions by resolving each
* against this configurer's set of {@link PropertySources}, which includes:
* <ul>
* <li>all {@linkplain org.springframework.core.env.ConfigurableEnvironment#getPropertySources
* environment property sources}, if an {@code Environment} {@linkplain #setEnvironment is present}
* <li>{@linkplain #mergeProperties merged local properties}, if {@linkplain #setLocation any}
* {@linkplain #setLocations have} {@linkplain #setProperties been}
* {@linkplain #setPropertiesArray specified}
* <li>any property sources set by calling {@link #setPropertySources}
* </ul>
* <p>If {@link #setPropertySources} is called, <strong>environment and local properties will be
* ignored</strong>. This method is designed to give the user fine-grained control over property
* sources, and once set, the configurer makes no assumptions about adding additional sources.
*/
@Override
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) {
this.propertySources.addFirst(localPropertySource);
}
else {
this.propertySources.addLast(localPropertySource);
}
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
}
这里我们找到了相关的属性值,并将其放入propertySources中,然后调用processProperties()方法。
/**
* 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() {
public String resolveStringValue(String strVal) {
String resolved = ignoreUnresolvablePlaceholders ?
propertyResolver.resolvePlaceholders(strVal) :
propertyResolver.resolveRequiredPlaceholders(strVal);
return (resolved.equals(nullValue) ? null : resolved);
}
};
doProcessProperties(beanFactoryToProcess, valueResolver);
}
我们继续往下看doProcessProperties方法:
PlaceholderConfigurerSupport中的doProcessProperties方法:
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
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);
}
这里我们关注下面的两个地方:
// 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);
这两步将这个valueResolver放入了BeanFactory中,我们再来看Bean实例化的时候:
我们知道Bean的实例化分为两步,第一步是通过cglib或者jdk的动态代理创建实例对象,第二步是进行相关属性的初始化的工作,我们着重来看下第二步的工作:
populateBean(beanName, mbd, instanceWrapper);
这个方法在AbstractAutowireCapableBeanFactory这个IOC容器中,不清楚Bean实例化过程的同学可以看一下之前的相关系列文章。
接下来我们就直接看相关的部分:
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
.................................................................
pvs =
ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
.................................................................
}
这里的ibp对象是实现了BeanPostProcesser的AutowiredAnnotationBeanPostProcessor对象:
@Override
public PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
return pvs;
}
这个里面的metadata.inject(bean,beanName.pvs)便是将相关属性的值注入到相关的对象Bean中,
public void inject(Object target, String beanName, PropertyValues pvs) throws Throwable {
Collection<InjectedElement> elementsToIterate =
(this.checkedElements != null ? this.checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
boolean debug = logger.isDebugEnabled();
for (InjectedElement element : elementsToIterate) {
if (debug) {
logger.debug("Processing injected element of bean '" + beanName + "': " + element);
}
element.inject(target, beanName, pvs);
}
}
}
这里的element是AutowiredFieldElement,因此element.inject(target,beanName.pvs)转化成了AutowiredAnnotationBeanPostProcessor中的:
private class AutowiredFieldElement extends InjectionMetadata.InjectedElement {
private final boolean required;
private volatile boolean cached = false;
private volatile Object cachedFieldValue;
public AutowiredFieldElement(Field field, boolean required) {
super(field, null);
this.required = required;
}
@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
try {
Object value;
if (this.cached) {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
else {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
Set<String> autowiredBeanNames = new LinkedHashSet<String>(1);
TypeConverter typeConverter = beanFactory.getTypeConverter();
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
synchronized (this) {
if (!this.cached) {
if (value != null || this.required) {
this.cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName)) {
if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
this.cachedFieldValue = new RuntimeBeanReference(autowiredBeanName);
}
}
}
}
else {
this.cachedFieldValue = null;
}
this.cached = true;
}
}
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
catch (Throwable ex) {
throw new BeanCreationException("Could not autowire field: " + field, ex);
}
}
}
其中:
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
便是获取占位符所代替的真实值,这里的beanfactory依旧是DefaultListableBeanFactory,因此我们看相关的resolveDependency方法:
public Object resolveDependency(DependencyDescriptor descriptor, String beanName,
Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
if (descriptor.getDependencyType().equals(ObjectFactory.class)) {
return new DependencyObjectFactory(descriptor, beanName);
}
else if (descriptor.getDependencyType().equals(javaxInjectProviderClass)) {
return new DependencyProviderFactory().createDependencyProvider(descriptor, beanName);
}
else {
return doResolveDependency(descriptor, descriptor.getDependencyType(), beanName, autowiredBeanNames, typeConverter);
}
}
protected Object doResolveDependency(DependencyDescriptor descriptor, Class<?> type, String beanName,
Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
if (value instanceof String) {
String strVal = resolveEmbeddedValue((String) value);
BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
value = evaluateBeanDefinitionString(strVal, bd);
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
return (descriptor.getField() != null ?
converter.convertIfNecessary(value, type, descriptor.getField()) :
converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}
.......................................................
}
这里我们只看相关的部分,我们可以看到这里有一个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;
}
这里我们可以看到,这和我们之前通过PlaceholderConfigurerSupport中:
// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
此处联系起来了,而这里由于有两个PropertySourcesPlaceholderConfigurer因此有两个resolver,我们看下这行代码:
result = resolver.resolveStringValue(result);
这里的result等于解析后的值,因此第一次解析{connectTimeout}之后值变成3000之后,后面config1.properties所对应的resolver来解析的时候已经变成了3000,因此已经无法解析了(解析需满足格式)因此result的值也就无法覆盖,也就是config.properties中的值了。
这就是为什么场景2中config1.properties无法覆盖config.properties中值的原因。
转载于:https://my.oschina.net/guanhe/blog/1501471
上一篇: HTML5 WebSocket