内容简介:spring里的占位符通常表现的形式是:或者Spring应用在有时会出现占位符配置没有注入,原因可能是多样的。
Spring里的占位符
spring里的占位符通常表现的形式是:
或者
Spring应用在有时会出现占位符配置没有注入,原因可能是多样的。
本文介绍两种比较复杂的情况。
占位符是在Spring生命周期的什么时候处理的
Spirng在生命周期里关于Bean的处理大概可以分为下面几步:
-
加载Bean定义(从xml或者从
@Import
等) -
处理
BeanFactoryPostProcessor
-
实例化Bean
-
处理Bean的property注入
-
处理
BeanPostProcessor
当然这只是比较理想的状态,实际上因为Spring Context在构造时,也需要创建很多内部的Bean,应用在接口实现里也会做自己的各种逻辑,整个流程会非常复杂。
那么占位符( ${}表达式
)是在什么时候被处理的?
-
实际上是在
org.springframework.context.support.PropertySourcesPlaceholderConfigurer
里处理的,它会访问了每一个bean的BeanDefinition,然后做占位符的处理 -
PropertySourcesPlaceholderConfigurer
实现了BeanFactoryPostProcessor
接口 -
PropertySourcesPlaceholderConfigurer
的 order是Ordered.LOWEST_PRECEDENCE
,也就是最低优先级的
结合上面的Spring的生命周期,如果Bean的创建和使用在 PropertySourcesPlaceholderConfigurer
之前,那么就有可能出现占位符没有被处理的情况。
例子1:Mybatis 的 MapperScannerConfigurer引起的占位符没有处理
例子代码:https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-mybatis-placeholder
-
首先应用自己在代码里创建了一个
DataSource
,其中${db.user}
是希望从application.properties
里注入的。代码在运行时会打印出user
的实际值。 -
然后应用用代码的方式来初始化mybatis相关的配置,依赖上面创建的
DataSource
对象
当代码运行时,输出结果是:
为什么会 user
这个变量没有被注入?
分析下Bean定义,可以发现 MapperScannerConfigurer
它实现了 BeanDefinitionRegistryPostProcessor
。这个接口在是Spring扫描Bean定义时会回调的,远早于 BeanFactoryPostProcessor
。
所以原因是:
-
MapperScannerConfigurer
它实现了BeanDefinitionRegistryPostProcessor
,所以它会Spring的早期会被创建 -
从bean的依赖关系来看,mapperScannerConfigurer依赖了sqlSessionFactory1,sqlSessionFactory1依赖了dataSource1
-
MyDataSourceConfig
里的dataSource1
被提前初始化,没有经过PropertySourcesPlaceholderConfigurer
的处理,所以@Value("${db.user}")Stringuser
里的占位符没有被处理
要解决这个问题,可以在代码里,显式来处理占位符:
例子2:Spring boot自身实现问题,导致Bean被提前初始化
例子代码:https://github.com/hengyunabc/spring-boot-inside/tree/master/demo-ConditionalOnBean-placeholder
Spring Boot里提供了 @ConditionalOnBean
,这个方便用户在不同条件下来创建bean。里面提供了判断是否存在bean上有某个注解的功能。
比如用户自己定义了一个Annotation:
然后用下面的写法来创建abc这个bean,意思是当用户显式使用了 @MyAnnotation
(比如放在main class上),才会创建这个bean。
这个功能很好,但是在spring boot 1.4.5 版本之前都有问题,会导致FactoryBean提前初始化。
在例子里,通过xml创建了 javaVersion
这个bean,想获取到 java 的版本号。这里使用的是spring提供的一个调用static函数创建bean的技巧。
我们在代码里获取到这个 javaVersion
,然后打印出来:
在实际运行时,发现javaVersion的值是null。
这个其实是spring boot的锅,要搞清楚这个问题,先要看 @ConditionalOnBean
的实现。
-
@ConditionalOnBean
实际上是在ConfigurationClassPostProcessor里被处理的,它实现了BeanDefinitionRegistryPostProcessor
-
BeanDefinitionRegistryPostProcessor
是在spring早期被处理的 -
@ConditionalOnBean
的具体处理代码在org.springframework.boot.autoconfigure.condition.OnBeanCondition
里 -
OnBeanCondition
在获取bean的Annotation时,调用了beanFactory.getBeanNamesForAnnotation
-
beanFactory.getBeanNamesForAnnotation
会导致FactoryBean
提前初始化,创建出javaVersion
里,传入的${java.version.key}
没有被处理,值为null。 -
spring boot 1.4.5 修复了这个问题:https://github.com/spring-projects/spring-boot/issues/8269
实现spring boot starter要注意不能导致bean提前初始化
用户在实现spring boot starter时,通常会实现Spring的一些接口,比如 BeanFactoryPostProcessor
接口,在处理时,要注意不能调用类似 beanFactory.getBeansOfType
, beanFactory.getBeanNamesForAnnotation
这些函数,因为会导致一些bean提前初始化。
而上面有提到 PropertySourcesPlaceholderConfigurer
的order是最低优先级的,所以用户自己实现的 BeanFactoryPostProcessor
接口在被回调时很有可能占位符还没有被处理。
对于用户自己定义的 @ConfigurationProperties
对象的注入,可以用类似下面的代码:
总结
-
占位符(${}表达式)是在
PropertySourcesPlaceholderConfigurer
里处理的,也就是BeanFactoryPostProcessor
接口 -
spring的生命周期是比较复杂的事情,在实现了一些早期的接口时要小心,不能导致spring bean提前初始化
-
在早期的接口实现里,如果想要处理占位符,可以利用spring自身的api,比如
environment.resolvePlaceholders("${db.user}")
推荐文章:
■ 深入Spring Boot:从JVM分析hibernate-validator NoClassDefFoundError
好看扫码关注
横云断岭的专栏
专注Java, Spring Boot, Arthas, Dubbo
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 自定义占位文本(placeholder)的样式
- ios – 在文本字段中居中对齐占位符
- 利用「占位块」弥补 space-between 的不足
- Python:使用变量名称占位符格式化字符串
- go-Printf占位符(之后有时间整理成表格)
- CKEditor 4.15 发布:新增编辑器占位符插件和历史颜色
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
ActionScript 3.0 Cookbook
Joey Lott、Darron Schall、Keith Peters / Adobe Dev Library / 2006-10-11 / GBP 28.50
Well before Ajax and Microsoft's Windows Presentation Foundation hit the scene, Macromedia offered the first method for building web pages with the responsiveness and functionality of desktop programs......一起来看看 《ActionScript 3.0 Cookbook》 这本书的介绍吧!