Spring源码:占位符${}解析源码分析

目录

1.Spring中用到的占位符解析类

2.PropertySourcesPlaceholderConfigurer实现bean工厂后处理器

3.PropertyPlaceholderHelper帮助类解析逻辑


在java spring项目中,我们经常使用到${}占位符实现属性值的获取,最常见的就是使用@Value("${propsKey}")的方式完成bean属性值的注入,如下:

 @Value("${appkey}")
 private String appkey;

下面重点分析一下spring中占位符解析的源码实现;

1.Spring中用到的占位符解析类

PropertySourcesPlaceholderConfigurer:从environment(包括properties文件以及注解PropertySource解析的属性)以及本地加载属性中解析占位符

PropertyPlaceholderConfigurer:从Properties和系统属性中解析占位符

目前,Spring容器中默认初始化的是PropertySourcesPlaceholderConfigurer

2个类具体的类继承层次如下:

Spring源码:占位符${}解析源码分析_第1张图片

可以看出,占位符解析类都是继承BeanFactoryPostProcessor bean工厂后处理器,作用就是对BeanDefinition中的占位符${}进行解析;

下面看一下PropertySourcesPlaceholderConfigurer具体的占位符解析过程;

2.PropertySourcesPlaceholderConfigurer实现bean工厂后处理器

 具体实现逻辑如下:

/**
	 * Processing occurs by replacing ${...} placeholders in bean definitions by resolving each
	 * against this configurer's set of {@link PropertySources}, which includes:
	 * 
    *
  • all {@linkplain org.springframework.core.env.ConfigurableEnvironment#getPropertySources * environment property sources}, if an {@code Environment} {@linkplain #setEnvironment is present} *
  • {@linkplain #mergeProperties merged local properties}, if {@linkplain #setLocation any} * {@linkplain #setLocations have} {@linkplain #setProperties been} * {@linkplain #setPropertiesArray specified} *
  • any property sources set by calling {@link #setPropertySources} *
*

If {@link #setPropertySources} is called, environment and local properties will be * ignored. 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_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) { @Override @Nullable 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)); this.appliedPropertySources = this.propertySources; }

这里看到主要分为三步:

  • 构造属性源,主要包含environment以及本地加载的属性

  • 构造属性解析器PropertySourcesPropertyResolver

  • 解析beanDefinition中的占位符

PropertySourcesPropertyResolver根据propertySources查找指定key的属性,属性值可能也包含了占位符,这里对属性值再次进行了一次占位符解析,实现逻辑如下:

@Nullable
	protected  T getProperty(String key, Class targetValueType, boolean resolveNestedPlaceholders) {
		if (this.propertySources != null) {
			for (PropertySource propertySource : this.propertySources) {
				if (logger.isTraceEnabled()) {
					logger.trace("Searching for key '" + key + "' in PropertySource '" +
							propertySource.getName() + "'");
				}
				Object value = propertySource.getProperty(key);
				if (value != null) {
					if (resolveNestedPlaceholders && value instanceof String) {
						value = resolveNestedPlaceholders((String) value);
					}
					logKeyFound(key, propertySource, value);
					return convertValueIfNecessary(value, targetValueType);
				}
			}
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Could not find key '" + key + "' in any property source");
		}
		return null;
	}

解析beanDefinition中的占位符主要用到了BeanDefinitionVisitor对象:

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

其中visitBeanDefinition方法对BeanDefinition中的属性进行占位符解析,如下:

    /**
	 * Traverse the given BeanDefinition object and the MutablePropertyValues
	 * and ConstructorArgumentValues contained in them.
	 * @param beanDefinition the BeanDefinition object to traverse
	 * @see #resolveStringValue(String)
	 */
	public void visitBeanDefinition(BeanDefinition beanDefinition) {
		visitParentName(beanDefinition);
		visitBeanClassName(beanDefinition);
		visitFactoryBeanName(beanDefinition);
		visitFactoryMethodName(beanDefinition);
		visitScope(beanDefinition);
		if (beanDefinition.hasPropertyValues()) {
			visitPropertyValues(beanDefinition.getPropertyValues());
		}
		if (beanDefinition.hasConstructorArgumentValues()) {
			ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
			visitIndexedArgumentValues(cas.getIndexedArgumentValues());
			visitGenericArgumentValues(cas.getGenericArgumentValues());
		}
     }
    /**
	 * Resolve the given String value, for example parsing placeholders.
	 * @param strVal the original String value
	 * @return the resolved String value
	 */
	@Nullable
	protected String resolveStringValue(String strVal) {
		if (this.valueResolver == null) {
			throw new IllegalStateException("No StringValueResolver specified - pass a resolver " +
					"object into the constructor or override the 'resolveStringValue' method");
		}
		String resolvedValue = this.valueResolver.resolveStringValue(strVal);
		// Return original String if not modified.
		return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
	}

3.PropertyPlaceholderHelper帮助类解析逻辑

默认的占位符前后缀分别为${ 和 } ,比较核心的字符串解析逻辑都在PropertyPlaceholderHelper的方法parseStringValue中,如下:

        protected String parseStringValue(
			String value, PlaceholderResolver placeholderResolver, @Nullable Set visitedPlaceholders) {

		int startIndex = value.indexOf(this.placeholderPrefix);
		if (startIndex == -1) {
			return value;
		}

		StringBuilder result = new StringBuilder(value);
		while (startIndex != -1) {
			int endIndex = findPlaceholderEndIndex(result, startIndex);
			if (endIndex != -1) {
				String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
				String originalPlaceholder = placeholder;
				if (visitedPlaceholders == null) {
					visitedPlaceholders = new HashSet<>(4);
				}
				if (!visitedPlaceholders.add(originalPlaceholder)) {
					throw new IllegalArgumentException(
							"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
				}
				// Recursive invocation, parsing placeholders contained in the placeholder key.
				placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
				// Now obtain the value for the fully resolved key...
				String propVal = placeholderResolver.resolvePlaceholder(placeholder);
				if (propVal == null && this.valueSeparator != null) {
					int separatorIndex = placeholder.indexOf(this.valueSeparator);
					if (separatorIndex != -1) {
						String actualPlaceholder = placeholder.substring(0, separatorIndex);
						String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
						propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
						if (propVal == null) {
							propVal = defaultValue;
						}
					}
				}
				if (propVal != null) {
					// Recursive invocation, parsing placeholders contained in the
					// previously resolved placeholder value.
					propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
					result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
					if (logger.isTraceEnabled()) {
						logger.trace("Resolved placeholder '" + placeholder + "'");
					}
					startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
				}
				else if (this.ignoreUnresolvablePlaceholders) {
					// Proceed with unprocessed value.
					startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
				}
				else {
					throw new IllegalArgumentException("Could not resolve placeholder '" +
							placeholder + "'" + " in value \"" + value + "\"");
				}
				visitedPlaceholders.remove(originalPlaceholder);
			}
			else {
				startIndex = -1;
			}
		}
		return result.toString();
	}

这里自顶而下对占位符进行解析,支持占位符的嵌套,同时能够检测占位符间的循环引用;

对解析出来的占位符,再利用PlaceholderResolver对占位符key进行解析和字符串替换;

你可能感兴趣的:(spring,PropertySource,属性配置,java,spring,spring,boot)