zoukankan      html  css  js  c++  java
  • 【spring源码分析】IOC容器初始化——查漏补缺(五)

    前言:我们知道在Spring中经常使用配置文件的形式对进行属性的赋值,那配置文件的值是怎么赋值到属性上的呢,本文将对其进行分析。


    首先了解一个类:PropertySourcesPlaceholderConfigurer,该类对程序中使用占位符的方式对属性进行赋值的形式进行解析,如在xml配置文件中进行了key-value的配置,在程序中使用该配置的值的形式。

    分析:

    从PropertySourcesPlaceholderConfigurer的继承结构图上可知,PropertySourcesPlaceholderConfigurer间接实现了Aware和BeanFactoryPostProcessor两大接口,这里只关注BeanFactoryPostProcessor(Aware接口前面已经分析了),BeanFactoryPostProcessor接口中就只有一个postProcessBeanFactory方法,其实现如下:

     1 // PropertySourcesPlaceholderConfigurer
     2 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
     3         // 如果propertySources为null,则初始化该属性
     4         if (this.propertySources == null) {
     5             this.propertySources = new MutablePropertySources();
     6             if (this.environment != null) {
     7                 this.propertySources.addLast(
     8                     new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
     9                         @Override
    10                         @Nullable
    11                         public String getProperty(String key) {
    12                             return this.source.getProperty(key);
    13                         }
    14                     }
    15                 );
    16             }
    17             try {
    18                 // 构建localPropertySource对象,Properties通过mergeProperties方法获取。
    19                 PropertySource<?> localPropertySource =
    20                         new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
    21                 if (this.localOverride) {
    22                     this.propertySources.addFirst(localPropertySource);
    23                 }
    24                 else {
    25                     this.propertySources.addLast(localPropertySource);
    26                 }
    27             }
    28             catch (IOException ex) {
    29                 throw new BeanInitializationException("Could not load properties", ex);
    30             }
    31         }
    32         // 进行占位符替换
    33         processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
    34         this.appliedPropertySources = this.propertySources;
    35     }

    分析:

    • 首先需要构建MutablePropertySources对象,用于存储Properties,注意Properties是通过mergeProperties获取。
    • 占位符的替换过程在processProperties函数中实现。

    PropertiesLoaderSupport#mergeProperties

     1 protected Properties mergeProperties() throws IOException {
     2         // 创建Properties对象
     3         Properties result = new Properties();
     4 
     5         // 是否允许覆盖配置 之前
     6         if (this.localOverride) {
     7             // Load properties from file upfront, to let local properties override.
     8             loadProperties(result);
     9         }
    10         
    11         // 进行配置合并
    12         if (this.localProperties != null) {
    13             for (Properties localProp : this.localProperties) {
    14                 CollectionUtils.mergePropertiesIntoMap(localProp, result);
    15             }
    16         }
    17         // 再次进行覆盖 之后
    18         if (!this.localOverride) {
    19             // Load properties from file afterwards, to let those properties override.
    20             loadProperties(result);
    21         }
    22 
    23         return result;
    24     }

    分析:

    这里其实逻辑简单,主要通过loadProperties进行配置文件的导入。

    PropertiesLoaderSupport#loadProperties

     1 protected void loadProperties(Properties props) throws IOException {
     2         // 遍历文件路径
     3         if (this.locations != null) {
     4             for (Resource location : this.locations) {
     5                 if (logger.isTraceEnabled()) {
     6                     logger.trace("Loading properties file from " + location);
     7                 }
     8                 try {
     9                     PropertiesLoaderUtils.fillProperties(
    10                             props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
    11                 } catch (FileNotFoundException | UnknownHostException ex) {
    12                     if (this.ignoreResourceNotFound) {
    13                         if (logger.isDebugEnabled()) {
    14                             logger.debug("Properties resource not found: " + ex.getMessage());
    15                         }
    16                     } else {
    17                         throw ex;
    18                     }
    19                 }
    20             }
    21         }
    22     }

    分析:

    这里代码逻辑简单,变量locations对象,然后通过PropertiesLoaderUtils#fillProperties方法进行属性填充,这里不对该方法进行分析,有兴趣的可以自己详细看下。

    PropertiesLoaderSupport#processProperties

     1 protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
     2             final ConfigurablePropertyResolver propertyResolver) throws BeansException {
     3         // 设置"${"、"}"、":"
     4         propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
     5         propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
     6         propertyResolver.setValueSeparator(this.valueSeparator);
     7 
     8         // 根据是否需要忽略不能解析的符号进行分支处理
     9         StringValueResolver valueResolver = strVal -> {
    10             String resolved = (this.ignoreUnresolvablePlaceholders ?
    11                     propertyResolver.resolvePlaceholders(strVal) :
    12                     propertyResolver.resolveRequiredPlaceholders(strVal));
    13             if (this.trimValues) {
    14                 resolved = resolved.trim();
    15             }
    16             return (resolved.equals(this.nullValue) ? null : resolved);
    17         };
    18         
    19         // 进行具体解析
    20         doProcessProperties(beanFactoryToProcess, valueResolver);
    21     }

    分析:

    从函数的具体逻辑可以看出Spring中对占位符的解析只包含"${ }"和":"。

    • 创建StringValueResolver对象,根据不同的策略(是否要忽略不能解析的符号),这里会在doProcessProperties中进行回调。其实resolvePlaceholders和resolveRequiredPlaceholders函数内部都调用的同一个函数,只是其初始化方式不同(false和true的区别),具体代码AbstractPropertyResolver中。
    • 最终的具体解析过程落在doProcessProperties函数中。

    AbstractPropertyResolver#resolveRequiredPlaceholders

     1 public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
     2         // 如果strictHelper为null,则通过createPlaceholderHelper进行创建 
     3         // 注意这里createPlaceholderHelper入参为false,这就是和resolvePlaceholders不一样的地方
     4         if (this.strictHelper == null) {
     5             this.strictHelper = createPlaceholderHelper(false);
     6         }
     7         // 通过PropertyPlaceholderHelper进行解析
     8         return doResolvePlaceholders(text, this.strictHelper);
     9 }
    10 
    11 private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
    12         return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
    13                 this.valueSeparator, ignoreUnresolvablePlaceholders);
    14     }
    15 
    16 private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
    17         return helper.replacePlaceholders(text, this::getPropertyAsRawString);
    18     }
    19 // PropertyPlaceholderHelper
    20 public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
    21         Assert.notNull(value, "'value' must not be null");
    22         return parseStringValue(value, placeholderResolver, new HashSet<>());
    23     }

    分析:

    以上代码逻辑还是比较简单,最终的解析落入PropertyPlaceholderHelper#replacePlaceHolders函数中,该函数在【spring源码分析】IOC容器初始化(一)中已分析。

    PlaceholderConfigurerSupport#doProcessProperties

     1 protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
     2             StringValueResolver valueResolver) {
     3 
     4         // 创建BeanDefinitionVisitor对象
     5         BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
     6 
     7         // 对BeanDefinition进行遍历
     8         String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
     9         for (String curName : beanNames) {
    10             // Check that we're not parsing our own bean definition,
    11             // to avoid failing on unresolvable placeholders in properties file locations.
    12             // 校验
    13             // 当前PlaceholderConfigurerSupport不在解析范围内  同一个Spring容器
    14             if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
    15                 BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
    16                 try {
    17                     // 访问bean,其实是对属性进行解析
    18                     visitor.visitBeanDefinition(bd);
    19                 }
    20                 catch (Exception ex) {
    21                     throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
    22                 }
    23             }
    24         }
    25 
    26         // New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
    27         // 别名占位符
    28         beanFactoryToProcess.resolveAliases(valueResolver);
    29 
    30         // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
    31         // 解析嵌入值的占位符,例如注释属性
    32         beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
    33     }

    分析:

    • 首先创建BeanDefinitionVisitor对象,通过字符串解析器,用于后期对配置文件进行解析。
    • 对BeanDefinition进行遍历,通过visitBeanDefinition方法进行配置文件解析。
    • 解析别名占位符。
    • 解析嵌入值的占位符,例如注释属性。

    其实整个方法的核心在于visitBeanDefinition方法。

    BeanDefinitionVisitor#visitBeanDefinition

     1 public void visitBeanDefinition(BeanDefinition beanDefinition) {
     2         visitParentName(beanDefinition);
     3         visitBeanClassName(beanDefinition);
     4         visitFactoryBeanName(beanDefinition);
     5         visitFactoryMethodName(beanDefinition);
     6         visitScope(beanDefinition);
     7         if (beanDefinition.hasPropertyValues()) {
     8             visitPropertyValues(beanDefinition.getPropertyValues());
     9         }
    10         if (beanDefinition.hasConstructorArgumentValues()) {
    11             ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
    12             visitIndexedArgumentValues(cas.getIndexedArgumentValues());
    13             visitGenericArgumentValues(cas.getGenericArgumentValues());
    14         }
    15     }

    分析:

    该方法基本访问了BeanDefinition中所有值得访问的东西了,包括parent 、class 、factory-bean 、factory-method 、scope 、property 、constructor-arg。

    这里关注visitPropertyValues方法。

    BeanDefinitionVisitor#visitPropertyValues

     1     protected void visitPropertyValues(MutablePropertyValues pvs) {
     2         // 遍历PropertyValue数组
     3         PropertyValue[] pvArray = pvs.getPropertyValues();
     4         for (PropertyValue pv : pvArray) {
     5             // 解析真值
     6             Object newVal = resolveValue(pv.getValue());
     7             if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
     8                 // 设置到PropertyValue中
     9                 pvs.add(pv.getName(), newVal);
    10             }
    11         }
    12     }

    分析:

    该函数主要就是对属性数组进行遍历,通过resolveValue方法对属性进行解析获取最新值,如果新值与就只不相等,则用新值替换旧值。

    BeanDefinitionVisitor#resolveValue

     1 protected Object resolveValue(@Nullable Object value) {
     2         if (value instanceof BeanDefinition) {
     3             visitBeanDefinition((BeanDefinition) value);
     4         }
     5         else if (value instanceof BeanDefinitionHolder) {
     6             visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition());
     7         }
     8         else if (value instanceof RuntimeBeanReference) {
     9             RuntimeBeanReference ref = (RuntimeBeanReference) value;
    10             String newBeanName = resolveStringValue(ref.getBeanName());
    11             if (newBeanName == null) {
    12                 return null;
    13             }
    14             if (!newBeanName.equals(ref.getBeanName())) {
    15                 return new RuntimeBeanReference(newBeanName);
    16             }
    17         }
    18         else if (value instanceof RuntimeBeanNameReference) {
    19             RuntimeBeanNameReference ref = (RuntimeBeanNameReference) value;
    20             String newBeanName = resolveStringValue(ref.getBeanName());
    21             if (newBeanName == null) {
    22                 return null;
    23             }
    24             if (!newBeanName.equals(ref.getBeanName())) {
    25                 return new RuntimeBeanNameReference(newBeanName);
    26             }
    27         }
    28         else if (value instanceof Object[]) {
    29             visitArray((Object[]) value);
    30         }
    31         else if (value instanceof List) {
    32             visitList((List) value);
    33         }
    34         else if (value instanceof Set) {
    35             visitSet((Set) value);
    36         }
    37         else if (value instanceof Map) {
    38             visitMap((Map) value);
    39         }
    40         else if (value instanceof TypedStringValue) {
    41             TypedStringValue typedStringValue = (TypedStringValue) value;
    42             String stringValue = typedStringValue.getValue();
    43             if (stringValue != null) {
    44                 String visitedString = resolveStringValue(stringValue);
    45                 typedStringValue.setValue(visitedString);
    46             }
    47         }
    48         // 由于Properties中的是String,所以重点在此
    49         else if (value instanceof String) {
    50             return resolveStringValue((String) value);
    51         }
    52         return value;
    53     }

    分析:

    该函数对各种类型的属性进行解析,由于配置为String类型的,因此这里关注resolveStringValue函数。

    BeanDefinitionVisitor#resolveStringValue

     1 protected String resolveStringValue(String strVal) {
     2         if (this.valueResolver == null) {
     3             throw new IllegalStateException("No StringValueResolver specified - pass a resolver " +
     4                     "object into the constructor or override the 'resolveStringValue' method");
     5         }
     6         // 解析真值
     7         String resolvedValue = this.valueResolver.resolveStringValue(strVal);
     8         // Return original String if not modified.
     9         return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
    10     }

    分析:

    这里具体的解析就会回调valueResolver(根据不同策略创建的StringValueResolver对象),然后进入具体的解析,其解析过程已经分析,这里不在赘述。

    总结

    至此关于占位符的解析过程就大致分析完了,其实里面还有很多值得我们细究的地方,具体过程可debug调试一遍,可能会有更深的理解。


    by Shawn Chen,2019.05.08,晚。

  • 相关阅读:
    初尝2D骨骼动画编辑工具SPINE,并into Unity3D
    Unity3D优化总结(一)
    ARPG游戏打击感相关的技术简单总结
    总结一下一般游戏中3D模型各种勾边方法遇到的工程性问题
    多次被坑了后,重温设计模式原则
    unity shader序列帧动画代码,顺便吐槽一下unity shader系统
    暗黑三泛光算法研究
    《全民无双》锁链刀光程序生成算法,即U3D——刀光轨迹插件的改进
    Low-level Thinking in High-level Shading Languages
    python实现智能语音天气预报
  • 原文地址:https://www.cnblogs.com/developer_chan/p/10815510.html
Copyright © 2011-2022 走看看