欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring技术内幕5——利用property-placeholder节点配置Bean属性源码解析

程序员文章站 2022-05-31 18:45:50
...

    我们在基于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这个类,我们来看下这个类的继承关系:

Spring技术内幕5——利用property-placeholder节点配置Bean属性源码解析

我们可以看到这个类继承了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