BeanFactory后置处理器之PropertySourcesPlaceholderConfigurer
有的时候,我们需要读取配置文件中的属性,将其作为成员变量赋给对应的Bean,如下通过xml配置:
<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.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
又或者,使用@Value注解,通过Java代码配置:
public class JdbcBean {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.user}")
private String user;
@Value("${jdbc.password}")
private String password;
}
那么这个是如何实现的呢?原来,Spring提供了一种配置解析的功能,在Spring3.1版本之前是通过PropertyPlaceholderConfigurer实现的。而3.1之后则是通过PropertySourcesPlaceholderConfigurer 实现的。Spring已经发展到5.x了,所以今天我们主要来解析一下PropertySourcesPlaceholderConfigurer 。
自定义一个PropertySourcesPlaceholderConfigurer
我们可以在代码中,创建一个PropertySourcesPlaceholderConfigurer,并指定它解析的配置文件地址,如下:
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
PropertySourcesPlaceholderConfigurer propertySources = null;
try{
propertySources = new PropertySourcesPlaceholderConfigurer();
ClassPathResource classPathResource = new ClassPathResource("application.properties");
propertySources.setLocation(classPathResource);
}catch(Exception ex){
ex.printStackTrace();
}
return propertySources;
}
使用注解方式
@Component
@PropertySource("classpath:application.properties")
public class JdbcBean {}
Spring Bean的创建过程
首先我们来看一下Bean的创建过程(AbstractApplicationContext的refresh过程中的一些调用),由于这个过程在这里不是我们主要要讲解的,所以大略体会一下,并且记住其中有一个叫BeanFactoryPostProcessor的,后面我们还会提到它,创建过程如下:
- 实例化BeanFactoryPostProcessor实现类
- 调用BeanFactoryPostProcessor#postProcessBeanFactory
- 实例化BeanPostProcessor实现类
- 调用InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
- 实例化Bean
- 调用InstantiationAwareBeanProcessor#postProcessAfterInstantiation
- 调用InstantiationAwareBeanPostProcessor#postProcessPropertyValues
- 为Bean注入属性
- 调用BeanNameAware#setBeanName
- 调用BeanClassLoaderAware#setBeanClassLoader
- 调用BeanFactoryAware#setBeanFactory
- 调用BeanPostProcessor#postProcessBeforeInitialization
- 调用InitializingBean#afterPropertiesSet
- 调用Bean的init-method
- 调用BeanPostProcessor#postProcessAfterInitialization
源码分析
数据源加载
在Spring3.1之后,建议使用PropertySourcesPlaceholderConfigurer来取代PropertyPlaceholderConfigurer。
可以发现,其实PropertySourcesPlaceholderConfigurer是BeanFactoryPostProcessor的一个子类,所以在Bean的创建过程中可以得知,在执行过程中会调用postProcessBeanFactory,所以我们查找下对应的方法,定义如下:
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (this.propertySources == null) {
this.propertySources = new MutablePropertySources();
if (this.environment != null) {
this.propertySources.addLast(
new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
@Override
@Nullable
public String getProperty(String key) {
return this.source.getProperty(key);
}
}
);
}
try {
PropertySource<?> localPropertySource =
new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
if (this.localOverride) {
this.propertySources.addFirst(localPropertySource);
}
else {
this.propertySources.addLast(localPropertySource);
}
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
//处理属性
processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
this.appliedPropertySources = this.propertySources;
}
我们其实不难发现,属性来源分为两种:
- 以Environment为属性源的environmentProperties
- 通过loadProperties(Properties props)加载本地资源文件作为属性源的localProperties。属性源加载完毕后,将占位符替换为属性源中的属性。
占位符解析
属性源都加载完毕,接下来就是占位符的填充,源码如下:
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
final ConfigurablePropertyResolver propertyResolver) throws BeansException {
//this.placeholderPrefix为 ${
propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
//this.placeholderSuffix为 }
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
//this.valueSeparator为 :
propertyResolver.setValueSeparator(this.valueSeparator);
// 使用lambda表达式创建一个StringValueResolver
StringValueResolver valueResolver = strVal -> {
// 解析占位符,此处只能解析占位符
String resolved = (ignoreUnresolvablePlaceholders ?
propertyResolver.resolvePlaceholders(strVal) :
propertyResolver.resolveRequiredPlaceholders(strVal));
if (trimValues) {
resolved = resolved.trim();
}
return (resolved.equals(nullValue) ? null : resolved);
};
// 调用父类的doProcessProperties 把属性扫描到Bean的身上去
doProcessProperties(beanFactoryToProcess, valueResolver);
}
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
for (String curName : beanNames) {
//排除自身&&必须是同一个beanFactory
if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
visitor.visitBeanDefinition(bd);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
}
}
}
// 解析别名目标名称和别名中的占位符
beanFactoryToProcess.resolveAliases(valueResolver);
//在嵌入值(例如注释属性)中解析占位符
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
上面就是对PropertySourcesPlaceholderConfigurer工作原理的源码解析,概括来说分为两步:
- 属性源装配
- environmentProperties
- localProperties
- 占位符解析
- 解析占位符中的key
- 将key替换成对应的属性值
PropertySourcesPlaceholderConfigurer因为汇聚了Environment、多个PropertySource;所以它能够控制取值优先级、顺序,并且还提供了访问的方法,后期再想获取也不成问题。
@Autowired
private ApplicationContext applicationContext;
// 通过它,可以把生效的配置都拿到
@Autowired
private PropertySourcesPlaceholderConfigurer configurer;
public void getData() {
Environment environment = applicationContext.getEnvironment();
PropertySources appliedPropertySources = configurer.getAppliedPropertySources();
System.out.println(environment.containsProperty("bean.scope")); //false 注意环境里是没有这个key的
System.out.println(appliedPropertySources);
// 获取环境的和我们自己导入的
PropertySource<?> envProperties = appliedPropertySources.get(PropertySourcesPlaceholderConfigurer.ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME);
PropertySource<?> localProperties = appliedPropertySources.get(PropertySourcesPlaceholderConfigurer.LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME);
System.out.println(envProperties.getSource() == environment); //true 可以看到这个envProperties的source和环境里的是同一个
System.out.println(localProperties.containsProperty("bean.scope"));//true 本地配置里是包含这个属性的
}
还有另外一个类PropertyOverrideConfigurer,PropertyOverrideConfigurer类似于PropertySourcesPlaceholderConfigurer,与PropertyPlaceholderConfigurer 不同的是:PropertyOverrideConfigurer 利用属性文件的相关信息,覆盖XML 配置文件中定义。即PropertyOverrideConfigurer允许XML 配置文件中有默认的配置信息。
需要注意的是Properties属性文件:
beanName.property=value //第一个.前面一定是beanName
请保证这个beanName一定存在。它会根据beanName找到这个bean,然后override这个bean的相关属性值的。
因为这个类使用得相对较少,但使用步骤基本同上,因此此处就不再叙述了。