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 )
On the way
实例化
refers to the generation of a Java object.
Element injection timing
Element injection depends on AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues
1 . 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
实现类的方法进行执行。 仅此而已
可以看出
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
)
- ContextNamespaceHandler#init
- 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本身的设计决定的。
- Servlet的启动,在web.xml中Listener串行单线程启动。
- Spring内置的Bean模式为单例模式。
- Spring在初始化的时候会直接将需要的Bean给初始化成功。
- 启动的先后顺序依赖于它们在 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 />
或者PropertySourcesPlaceholderConfigurer
Bean来添加配置文件的名称。流程如下:
- Spring Context 的初始化开始
- 读取到
context:property-placeholder
标签或者PropertySourcesPlaceholderConfigurer
- 解析并实例化一个
PropertySourcesPlaceholderConfigurer
。同时向其中注入配置文件路径、名称 PropertySourcesPlaceholderConfigurer
自身生成多个StringValueResolver
备用,Bean准备完毕- Spring在初始化非
BeanFactoryPostProcessor
的Bean的时候,AutowiredAnnotationBeanPostProcessor
负责找到Bean内有@Value
注解的Field
或者Method
- 通过
PropertySourcesPlaceholderConfigurer
寻找合适的StringValueResolver
并解析得到val值。注入给@Value
的Field
或Method
。(Method优先
)2 - Spring的其他流程。