spring占位符

本文详细介绍了Spring中${}和@Value占位符的解析过程,涉及PropertySource、PropertySources、PropertyResolver和PropertiesLoaderSupport等核心组件。PropertySource作为键值对存储,PropertySources是其集合,PropertyResolver用于解析占位符并进行类型转换,而PropertiesLoaderSupport的子类PropertySourcesPlaceholderConfigurer实现了占位符的处理和Environment的接入。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

spring中使用的${} 以及@Value 注解的占位符,都在是spring容器初始化bean前,通过反射及类型转化把 占位符上的值转化为真实的值。

主要使用了 PropertySource、 PropertySources、PropertyResolver(PropertySourcesPropertyResolver)、PropertiesLoaderSupport(PropertySourcesPlaceholderConfigurer)类。

1、PropertySource

这个是org.springframework.core.env. PropertySource 抽象类,不是org.springframework.context.annotation.PropertySource注解哈。这个基类用来存key/value 键值对的,像Map、Properties类型的。

首先说下,这两个静态变量,因为定义为final 类型的 必须在构造方法中赋值。

这里的name 和 PropertSource 是有对应关系的,我们看下PropertySource 重写的equeals方法:

只要两个PropertSource 类的name值一样,那么就认为这是两个相同的PropertSource 对象。这就像我们使用@ PropertSource 注解在加载文件时,name就相当于被加载.prop文件的全路径名,如果两个文件名一样,那么就认为对应文件的内容是一样的。

      在说这里的PropertSource<T> 抽象类,可以传入一个泛型的对象,这个对象用来处理这个抽象类的抽象方法:

/**
	 * Return the value associated with the given name,
	 * or {@code null} if not found.
	 * @param name the property to find
	 * @see PropertyResolver#getRequiredProperty(String)
	 */
	public abstract Object getProperty(String name);

像 MapPropertySource 类 传入  Map<String, Object> 对象,从而在getProperty方法里通过source.get(key) 的方式实现这个方法。

像PropertySourcesPlaceholderConfigurer类的postProcessBeanFactory方法中 通过传入Environment 类型的对象 ,通过Environment对象的source.getProperty(key)

方式实现这个方法。

 

2、PropertySources

package org.springframework.core.env;

/**
 * Holder containing one or more {@link PropertySource} objects.
 *
 * @author Chris Beams
 * @since 3.1
 */
public interface PropertySources extends Iterable<PropertySource<?>> {

	/**
	 * Return whether a property source with the given name is contained.
	 * @param name the {@linkplain PropertySource#getName() name of the property source} to find
	 */
	boolean contains(String name);

	/**
	 * Return the property source with the given name, {@code null} if not found.
	 * @param name the {@linkplain PropertySource#getName() name of the property source} to find
	 */
	PropertySource<?> get(String name);

}

这接口相当于PropertySource 的集合类,为PropertySource 服务的。

 

2.1 MutablePropertySources

 

 这个类是对PropertyResources的子类,内部定义了一个 ArrayList 集合用于存放PropertySource 对象,

 

此外,这个类还有几个比较常用的方法:

其中 addFirst和addLast就是把对象放在List集合的最前面和最后面,很简单。

addBefore方法是把PropertySource对象放在指定的 对象前面,这里第一个参数是String name,根据这个name能找对集合中的目标PropertySource  (PropertySource 中定义的name 属性,使得name -à PropertySource 一 一对应),进而找到这个PropertySource 对象在ArrayList中的index(实际是在数组的第index个),然后把给定的PropertySource对象 放在集合的index的位置(在数组的第index位),其他的往后移动一位。

/**
	 * Add the given property source object with precedence immediately higher
	 * than the named relative property source.
	 */
	public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {
		if (logger.isDebugEnabled()) {
			logger.debug("Adding PropertySource '" + propertySource.getName() +
					"' with search precedence immediately higher than '" + relativePropertySourceName + "'");
		}
//1、确保不能把自己放在自己前面
		assertLegalRelativeAddition(relativePropertySourceName, propertySource);
// 2、若原list中存在propertySource,就把它删了
		removeIfPresent(propertySource);
//3、通过name找到需要放在PropertySource在数组中的索引位置
		int index = assertPresentAndGetIndex(relativePropertySourceName);
//4、把给定的propertySource放在数组的index位,后面的全部后移一位
		addAtIndex(index, propertySource);
	}

addAfter和addBefore 原理一样。

/**
	 * Add the given property source object with precedence immediately lower
	 * than the named relative property source.
	 */
	public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {
		if (logger.isDebugEnabled()) {
			logger.debug("Adding PropertySource '" + propertySource.getName() +
					"' with search precedence immediately lower than '" + relativePropertySourceName + "'");
		}
		assertLegalRelativeAddition(relativePropertySourceName, propertySource);
		removeIfPresent(propertySource);
		int index = assertPresentAndGetIndex(relativePropertySourceName);
		addAtIndex(index + 1, propertySource);
	}

3、PropertyResolver

这个接口是 Property解析器作用如下:

  1. 加载配置文件;
  2. 解析spring中的占位符 (如${}) ,进行类型转化,替换他们。

3.1 PropertySourcesPropertyResolver

看下这个类的构造方法:

所以在创建这个对象的时候必须传入 PropertySources ,这个接口是干啥的呢?存PropertySource<T>类型的集合啊, PropertySource<T>干啥的呢? 根据传入的泛型T,调用

public abstract Object getProperty(String name);

 

获取给定泛型中的key/value值。

继续看 PropertySourcesPropertyResolver 类的关键方法:

protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
		if (this.propertySources != null) {
 //1、在mutablePropertySources中的addfirst、addLast就是在这体现出来,放前面的先遍历呗
			for (PropertySource<?> propertySource : this.propertySources) {
				if (logger.isTraceEnabled()) {
					logger.trace("Searching for key '" + key + "' in PropertySource '" +
					propertySource.getName() + "'");
				}
//2、调用PropertySource的 getProperty方法
				Object value = propertySource.getProperty(key);
//3、去解决占位符以及类型转换的事去了
				if (value != null) {
					if (resolveNestedPlaceholders && value instanceof String) {
						value = resolveNestedPlaceholders((String) value);
					}
					logKeyFound(key, propertySource, value);
					return convertValueIfNecessary(value, targetValueType);
				}
			}
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Could not find key '" + key + "' in any property source");
		}
		return null;
	}

4、PropertiesLoaderSupport

这个是抽象类,是加载Properties的基类,

PlaceholderConfigurerSupport

这个类是为了解决spring中的占位符的问题,它有两个具体的实现子类,一个是PropertyPlaceholderConfigurer ,一个是PropertySourcesPlaceholderConfigurer。

目前官方已经不建议使用PropertyPlaceholderConfigurer,保留它只是为了兼容spring3.1 以前的版本。官方推荐的是PropertySourcesPlaceholderConfigurer,不仅可以解决像${},还可以解决带有@Value的替换,除此之外,PropertySourcesPlaceholderConfigurer类还实现了EnvironmentAware接口从而获取Environment对象,通过该对象实现PropertySource类中的getProperty 方法。

public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfigurer

        implements BeanNameAware, BeanFactoryAware

4.1 PropertySourcesPlaceholderConfigurer

public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware 

PropertyPlaceholderConfigurer是个bean工厂后置处理器的实现,也就是 BeanFactoryPostProcessor接口的一个实现。关于它的使用网上有很多的介绍,这里讲下原理,

PropertyResourceConfigurer 继承 BeanFactoryPostProcessor, PriorityOrdered

PlaceholderConfigurerSupport 继承 BeanNameAware, BeanFactoryAware

 

所以我们可以看到 PropertySourcesPlaceholderConfigurer 继承的有

BeanFactoryPostProcessor, PriorityOrdered,  BeanNameAware, BeanFactoryAware, EnvironmentAware 五个接口。

         最主要的方法是在实现BeanFactoryPostProcessor 接口的postProcessBeanFactory方法

@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		if (this.propertySources == null) {
			this.propertySources = new MutablePropertySources();
			if (this.environment != null) {
				this.propertySources.addLast(
//这里创建一个Propertysource< Environment >对象存在Propertysources中 new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
						@Override
						public String getProperty(String key) {
//这里的source就是environment对象							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);
			}
		}
//这里创建PropertySourcesPropertyResolver去解析上面加入的propertySources
processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
		this.appliedPropertySources = this.propertySources;
	}

 

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);
	}

doProcessProperties(beanFactoryToProcess, valueResolver);

 这个方法是PlaceholderConfigurerSupport 抽象类里实现的

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);
	}

这样就把这个占位符的解析StringValueResolver 对象交给beanFactory,在实例化对象的时候通过java的反射给属性或方法赋值,在赋值前 需要进行类型转化,因为StringValueResolver得到的值永远是String类型的,这就涉及到spring的转化器了。

 

关于@Value注解的转化,最终是由QualifierAnnotationAutowireCandidateResolver 这个类去处理的。详情可参见下面的文章:

https://www.jianshu.com/p/933669270a9f

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值