原文出处:
hengyunabc
Spring里的占位符
spring里的占位符通常表现的形式是:
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="url" value="${jdbc.url}"/> </bean>
或者
@Configuration @ImportResource("classpath:/com/acme/properties-config.xml") public class AppConfig { @Value("${jdbc.url}") private String url; }
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引起的占位符没有处理
例子代码:mybatis-demo.zip
- 首先应用自己在代码里创建了一个DataSource,其中${db.user}是希望从application.properties里注入的。代码在运行时会打印出user的实际值。
@Configuration public class MyDataSourceConfig { @Bean(name = "dataSource1") public DataSource dataSource1(@Value("${db.user}") String user) { System.err.println("user: " + user); JdbcDataSource ds = new JdbcDataSource(); ds.setURL("jdbc:h2:?/test"); ds.setUser(user); return ds; } }
- 然后应用用代码的方式来初始化mybatis相关的配置,依赖上面创建的DataSource对象
@Configuration public class MybatisConfig1 { @Bean(name = "sqlSessionFactory1") public SqlSessionFactory sqlSessionFactory1(DataSource dataSource1) throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); org.apache.ibatis.session.Configuration ibatisConfiguration = new org.apache.ibatis.session.Configuration(); sqlSessionFactoryBean.setConfiguration(ibatisConfiguration); sqlSessionFactoryBean.setDataSource(dataSource1); sqlSessionFactoryBean.setTypeAliasesPackage("sample.mybatis.domain"); return sqlSessionFactoryBean.getObject(); } @Bean MapperScannerConfigurer mapperScannerConfigurer(SqlSessionFactory sqlSessionFactory1) { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory1"); mapperScannerConfigurer.setBasePackage("sample.mybatis.mapper"); return mapperScannerConfigurer; } }
当代码运行时,输出结果是:
user: ${db.user}
为什么会user这个变量没有被注入?
分析下Bean定义,可以发现MapperScannerConfigurer它实现了BeanDefinitionRegistryPostProcessor。这个接口在是Spring扫描Bean定义时会回调的,远早于BeanFactoryPostProcessor。
所以原因是:
- MapperScannerConfigurer它实现了BeanDefinitionRegistryPostProcessor,所以它会Spring的早期会被创建
- 从bean的依赖关系来看,mapperScannerConfigurer依赖了sqlSessionFactory1,sqlSessionFactory1依赖了dataSource1
- MyDataSourceConfig里的dataSource1被提前初始化,没有经过Property