当前位置:   article > 正文

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

spring ${}

目录

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

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

3.PropertyPlaceholderHelper帮助类解析逻辑


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

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

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

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

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

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

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

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

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

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

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

 具体实现逻辑如下:

  1. /**
  2. * Processing occurs by replacing ${...} placeholders in bean definitions by resolving each
  3. * against this configurer's set of {@link PropertySources}, which includes:
  4. * <ul>
  5. * <li>all {@linkplain org.springframework.core.env.ConfigurableEnvironment#getPropertySources
  6. * environment property sources}, if an {@code Environment} {@linkplain #setEnvironment is present}
  7. * <li>{@linkplain #mergeProperties merged local properties}, if {@linkplain #setLocation any}
  8. * {@linkplain #setLocations have} {@linkplain #setProperties been}
  9. * {@linkplain #setPropertiesArray specified}
  10. * <li>any property sources set by calling {@link #setPropertySources}
  11. * </ul>
  12. * <p>If {@link #setPropertySources} is called, <strong>environment and local properties will be
  13. * ignored</strong>. This method is designed to give the user fine-grained control over property
  14. * sources, and once set, the configurer makes no assumptions about adding additional sources.
  15. */
  16. @Override
  17. public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
  18. if (this.propertySources == null) {
  19. this.propertySources = new MutablePropertySources();
  20. if (this.environment != null) {
  21. this.propertySources.addLast(
  22. new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
  23. @Override
  24. @Nullable
  25. public String getProperty(String key) {
  26. return this.source.getProperty(key);
  27. }
  28. }
  29. );
  30. }
  31. try {
  32. PropertySource<?> localPropertySource =
  33. new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
  34. if (this.localOverride) {
  35. this.propertySources.addFirst(localPropertySource);
  36. }
  37. else {
  38. this.propertySources.addLast(localPropertySource);
  39. }
  40. }
  41. catch (IOException ex) {
  42. throw new BeanInitializationException("Could not load properties", ex);
  43. }
  44. }
  45. processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
  46. this.appliedPropertySources = this.propertySources;
  47. }

这里看到主要分为三步:

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

  • 构造属性解析器PropertySourcesPropertyResolver

  • 解析beanDefinition中的占位符

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

  1. @Nullable
  2. protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
  3. if (this.propertySources != null) {
  4. for (PropertySource<?> propertySource : this.propertySources) {
  5. if (logger.isTraceEnabled()) {
  6. logger.trace("Searching for key '" + key + "' in PropertySource '" +
  7. propertySource.getName() + "'");
  8. }
  9. Object value = propertySource.getProperty(key);
  10. if (value != null) {
  11. if (resolveNestedPlaceholders && value instanceof String) {
  12. value = resolveNestedPlaceholders((String) value);
  13. }
  14. logKeyFound(key, propertySource, value);
  15. return convertValueIfNecessary(value, targetValueType);
  16. }
  17. }
  18. }
  19. if (logger.isTraceEnabled()) {
  20. logger.trace("Could not find key '" + key + "' in any property source");
  21. }
  22. return null;
  23. }

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

  1. protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
  2. StringValueResolver valueResolver) {
  3. BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
  4. String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
  5. for (String curName : beanNames) {
  6. // Check that we're not parsing our own bean definition,
  7. // to avoid failing on unresolvable placeholders in properties file locations.
  8. if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
  9. BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
  10. try {
  11. visitor.visitBeanDefinition(bd);
  12. }
  13. catch (Exception ex) {
  14. throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
  15. }
  16. }
  17. }
  18. // New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
  19. beanFactoryToProcess.resolveAliases(valueResolver);
  20. // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
  21. beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
  22. }

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

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

3.PropertyPlaceholderHelper帮助类解析逻辑

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

  1. protected String parseStringValue(
  2. String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
  3. int startIndex = value.indexOf(this.placeholderPrefix);
  4. if (startIndex == -1) {
  5. return value;
  6. }
  7. StringBuilder result = new StringBuilder(value);
  8. while (startIndex != -1) {
  9. int endIndex = findPlaceholderEndIndex(result, startIndex);
  10. if (endIndex != -1) {
  11. String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
  12. String originalPlaceholder = placeholder;
  13. if (visitedPlaceholders == null) {
  14. visitedPlaceholders = new HashSet<>(4);
  15. }
  16. if (!visitedPlaceholders.add(originalPlaceholder)) {
  17. throw new IllegalArgumentException(
  18. "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
  19. }
  20. // Recursive invocation, parsing placeholders contained in the placeholder key.
  21. placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
  22. // Now obtain the value for the fully resolved key...
  23. String propVal = placeholderResolver.resolvePlaceholder(placeholder);
  24. if (propVal == null && this.valueSeparator != null) {
  25. int separatorIndex = placeholder.indexOf(this.valueSeparator);
  26. if (separatorIndex != -1) {
  27. String actualPlaceholder = placeholder.substring(0, separatorIndex);
  28. String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
  29. propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
  30. if (propVal == null) {
  31. propVal = defaultValue;
  32. }
  33. }
  34. }
  35. if (propVal != null) {
  36. // Recursive invocation, parsing placeholders contained in the
  37. // previously resolved placeholder value.
  38. propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
  39. result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
  40. if (logger.isTraceEnabled()) {
  41. logger.trace("Resolved placeholder '" + placeholder + "'");
  42. }
  43. startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
  44. }
  45. else if (this.ignoreUnresolvablePlaceholders) {
  46. // Proceed with unprocessed value.
  47. startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
  48. }
  49. else {
  50. throw new IllegalArgumentException("Could not resolve placeholder '" +
  51. placeholder + "'" + " in value \"" + value + "\"");
  52. }
  53. visitedPlaceholders.remove(originalPlaceholder);
  54. }
  55. else {
  56. startIndex = -1;
  57. }
  58. }
  59. return result.toString();
  60. }

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/534016
推荐阅读
相关标签
  

闽ICP备14008679号