How Spring PropertySourcesPlaceholderConfigurer works

https://blog.csdn.net/xczzmn/article/details/77744627


foreword

Spring provides a configuration parsing function, which is this:

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" 
        init-method="init" destroy-method="close">
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

or this:

@Value("${spring_only}")
private String springOnly;
  • 1
  • 2
  • 1
  • 2
  • 1
  • 2

It is very convenient to inject configuration files under the Classpath by configuring XML. 
It was implemented by Spring version 3.1 PropertyPlaceholderConfigurer
After 3.1, it was PropertySourcesPlaceholderConfigurer achieved through.

There is no essential difference in usage between PropertyPlaceholderConfigurer and PropertyPlaceholderConfigurer. The fundamental goal of both is to generate KV pairs from configuration files. The real injection work is not performed by them themselves.

Spring life cycle

Configuration can achieve injection, it must follow the Spring life cycle. The life cycle of Spring Bean can refer to.

As shown below (image source: http://blog.csdn.net/qyp199312/article/details/60762194 )

Spring life cycle diagram 1

On the way 实例化refers to the generation of a Java object.

Element injection timing

Element injection depends on  AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues1 . PropertySourcesPlaceholderConfigurer Get the element value from the parsed query. If not, an exception is thrown. The following source code:

Source code taken from DefaultListableBeanFactory#doResolveDependency

// 获取注解的 value() 值。被写死为 Class<? extends Annotation> valueAnnotationType = Value.class;
// 见类 QualifierAnnotationAutowireCandidateResolver
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
    if (value instanceof String) {
        // 通过PropertySourcesPlaceholderConfigurer写入的键值对元素获取元素的值.
        // 方法内注册了多个StringValueResolver,循环查找值。提供者为PropertySourcesPlaceholderConfigurer,因此配置多个解析器的时候是以最后的配置为准的。
        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()));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

The sequence diagram of element injection is as follows:

在Spring初始化流程中,执行 AbstractApplicationContext#finishBeanFactoryInitializatin 方法。 该方法里面发生的主要流程为 Spring 业务 Bean初始化。 实际流程跟Spring Bean的初始化没有任务区别。 
通过对接口 InstantiationAwareBeanPostProcessor 实现类的方法进行执行。 仅此而已

Created with Raphaël 2.1.0AutowiredAnnotationBeanPostProcessorAutowiredAnnotationBeanPostProcessorInjectionMetadataInjectionMetadataInjectedElementInjectedElement这个类是 InstantiationAwareBeanPostProcessor的一个实现类, 用于 @Value和@Autowired注解实际执行方法postProcessPropertyValues调度实际调度InjectedElement子类被注入值的获取来自于DefaultListableBeanFactory将@Value(“${needReplace}”)里面的值替换的来源值,就是PropertySourcesPlaceholderConfigurer生成的StringValueResolver。而Spring原生的Bean是单例的它直接被储存在了AbstractBeanFactory执行Field.set(Object, Object)或者Method.invoke(Object, Object[])

可以看出 PropertySourcesPlaceholderConfigurer 或者 PropertyPlaceholderConfigurer 仅仅是做了一个配置文件的解析工作,真正的注入并不由它们完成,而是托付给了Spring 的Bean初始化流程。 
之所以这么做可以生效,是因为这两个类实现了 BeanFactoryPostProcessor 接口,这个接口的优先级高于后续的Spring Bean。

数据来源

配置Bean方式

单个配置文件

<bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
   <property name="location">
     <value>conf/sqlmap/jdbc.properties</value>
   </property>
    <property name="fileEncoding">
      <value>UTF-8</value>
    </property>
</bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

多个配置文件

<bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
    <property name="locations">
        <list>
            <value>/WEB-INF/mail.properties</value>  
            <value>classpath: conf/sqlmap/jdbc.properties</value>//注意这两种value值的写法
     </list>
    </property>
</bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

其中PropertyPlaceholderConfigurer是Spring3.1之前使用的。 
PropertySourcesPlaceholderConfigurer是Spring3.1之后使用的。 
写法都类似

Spring标签方式

<context:property-placeholder location="classpath*:/WEB-INF/mail.properties" />
  • 1
  • 1
  • 1

这总方式的原理就是构造一个PropertySourcesPlaceholderConfigurer, (3.1之前是PropertyPlaceholderConfigurer)

  1. ContextNamespaceHandler#init
  2. PropertyPlaceholderBeanDefinitionParser#doParse

触发点为: 
AbstractApplicationContext#obtainFreshBeanFactory 。Spring初始化Context的时候读取XML配置(基于XML), 这个流程优先于Spring 普通Bean初始化。配合扫包(<context:component-scan />)得到的Bean进而实现对XML里面配置的Bean的载入。

PropertySourcesPlaceholderConfigurer本质上是一个BeanFactoryPostProcessor。解析XML的流程在BeanFactoryPostProcessor之前, 优先将配置文件的路径以及名字通过Setter传入PropertySourcesPlaceholderConfigurer

如上BeanFactoryPostProcessor的优先级又优于其余的Bean。因此可以实现在bean初始化之前的注入。


引申mybatis数据源配置

通常在配置mybatis的时候,配置 org.mybatis.spring.mapper.MapperScannerConfigurer需要使用<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryAccount" /> 参数实现SqlSessionFactory的注入。

这是由于MapperScannerConfigurer 本质上也是一个 BeanFactoryPostProcessor。 而SqlSessionFactory往往只是一个普通的Spring Bean, 它的优先级是低于 MapperScannerConfigurer 的, 如果在初始化 MapperScannerConfigurer 的时候去寻找SqlSessionFactory, 肯定是会报依赖错误的, 因此之后在后续的流程中实现注入。

发生找不到配置的情况

在工作中我们会习惯性的使用多个Spring配置文件, 例如spring.xml/spring-db.xml/spring-web.xml灯。 里面就配置多个Spring标签,或者多个 PropertySourcesPlaceholderConfigurer 。但是时常会发生找不到配置的情况。

为什么<context:property-placeholder />优先级更高

因为基于XML配置的Spring, 不管是<context:property-placeholder />还是 <Bean />标签都依赖于 NamespaceHandler去解析。 而<context:property-placeholder />在解析完毕之后就已经生成了 PropertySourcesPlaceholderConfigurer , <Bean />标签还需要等待后续流程。


为什么PropertySourcesPlaceholderConfigurer唯一

这里的”为什么”, 指的不是它为什么这么设计,而是为什么会有这么样的结果。 
另外 PropertySourcesPlaceholderConfigurer 并不唯一,只是在对外体现上后续的配置无法去到值,因此看起来是唯一的。

PropertySourcesPlaceholderConfigurer Bean的唯一是由Java web特性和Spring本身的设计决定的。

  1. Servlet的启动,在web.xml中Listener串行单线程启动。
  2. Spring内置的Bean模式为单例模式。
  3. Spring在初始化的时候会直接将需要的Bean给初始化成功。
  4. 启动的先后顺序依赖于它们在 xml 里面配置的上下关系。

不管 <context:property-placeholder />或者PropertySourcesPlaceholderConfigurer或者PropertyPlaceholderConfigurer方式配置的配置解析器,其本质就是得到一个 BeanFactoryPostProcessor, 并执行其 #postProcessBeanFactory(ConfigurableListableBeanFactory)方法。

其执行的根本目的在于 PlaceholderConfigurerSupport#doProcessProperties(ConfigurableListableBeanFactory, StringValueResolver )

    protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
            StringValueResolver valueResolver) {
        // ignore
        ....

        // 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);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

这里的 addEmbeddedValueResolver(StringValueResolver) 是为一个 LinkedList添加值。

在取用的时候是优先从链表头开始取用的。 一旦发现无法找到值,直接就抛异常了。这个就对外体现出 PropertySourcesPlaceholderConfigurer 的唯一性。 (然而Spring内部还是有多个PropertySourcesPlaceholderConfigurer, 只不过除了排列在队首的 PropertySourcesPlaceholderConfigurer 之外全都被忽略掉了 )。

总结Spring Value注入流程

最后的总结:

配置Spring @Value("val2Inject") 方式获取配置文件的属性,需要依赖于在Spring XML里面配置<context:property-placeholder /> 或者PropertySourcesPlaceholderConfigurerBean来添加配置文件的名称。流程如下:

  1. Spring Context 的初始化开始
  2. 读取到context:property-placeholder标签或者PropertySourcesPlaceholderConfigurer
  3. 解析并实例化一个PropertySourcesPlaceholderConfigurer。同时向其中注入配置文件路径、名称
  4. PropertySourcesPlaceholderConfigurer自身生成多个StringValueResolver备用,Bean准备完毕
  5. Spring在初始化非BeanFactoryPostProcessor的Bean的时候,AutowiredAnnotationBeanPostProcessor 负责找到Bean内有@Value注解的Field或者Method
  6. 通过PropertySourcesPlaceholderConfigurer寻找合适的StringValueResolver并解析得到val值。注入给@ValueFieldMethod。(Method优先)2
  7. Spring的其他流程。

  1. This AutowiredAnnotationBeanPostProcessoris responsible for the parsing of @Autowiredthe @Valuetwo annotations. Parsing the @WebServiceRefsum @EJBand Resourcethe three annotations is CommonAnnotationBeanPostProcessor 
  2. @AutowiredThe injection of is also happening here 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326395619&siteId=291194637