Spring之PropertyPlaceholderConfigurer源码分析
一 引言
第一部分先说说在Spring下,怎么使用PropertyPlaceholderConfigurer及其原理。
第二部分再说说SpringBoot下,新的PropertySourcesPlaceholderConfigurer
二 代码示例
如果我们想在代码中使用@Value之类的注解,就需要在Spring的配置文件里这么写
<bean id="propertyPlaceholderConfigurer" class="org.springframework,beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>jdbc.properties<value/>
</list>
</property>
</bean>
要分析的重点就是 PropertyPlaceholderConfigurer,而它的父类是 PropertyResourceConfigurer
public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport implements BeanFactoryPostProcessor, PriorityOrdered
PropertyResourceConfigurer是一个BeanFactoryPostProcessor,熟悉Spring的都知道,Spring容器会首先初始化BeanFactoryPostProcessor,当其他的Bean还是BeanDefinition的时候,BeanFactoryPostProcessor就已经初始化好了。
BeanFactoryPostProcessor是接口,它有定义的方法 void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
我们看看在PropertyResourceConfigurer中是怎么实现的
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { try { Properties mergedProps = mergeProperties();//解析指定的属性文件 // Convert the merged properties, if necessary. convertProperties(mergedProps);//做值得转换 // Let the subclass process the properties. processProperties(beanFactory, mergedProps);//交给子类来实现 } catch (IOException ex) { throw new BeanInitializationException("Could not load properties", ex); } }
protected Properties mergeProperties() throws IOException { Properties result = new Properties(); if (this.localOverride) { // Load properties from file upfront, to let local properties override. loadProperties(result); } if (this.localProperties != null) { for (Properties localProp : this.localProperties) { CollectionUtils.mergePropertiesIntoMap(localProp, result); } } if (!this.localOverride) { // Load properties from file afterwards, to let those properties override. loadProperties(result); } return result; }
locations就是我们输入的多个配置文件,它会遍历每一个配置文件把里面的属性读出来,
protected void loadProperties(Properties props) throws IOException { if (this.locations != null) { for (Resource location : this.locations) { if (logger.isDebugEnabled()) { logger.debug("Loading properties file from " + location); } try { PropertiesLoaderUtils.fillProperties( props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister); }
继续看子类实现的processProperties
PropertyPlaceholderConfigurer.processProperties
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException { StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props); doProcessProperties(beanFactoryToProcess, valueResolver); }
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, StringValueResolver valueResolver) { BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver); String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames(); for (String curName : beanNames) { // Check that we're not parsing our own bean definition, // to avoid failing on unresolvable placeholders in properties file locations. if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) { BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName); try { visitor.visitBeanDefinition(bd);//处理BeadDefinition里的属性,BD中的属性有类型,比如Map list 引用类型等,如果在此时能够把值填充上就进行处理 } catch (Exception ex) { throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex); } } } // 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);//这里很关键,因为@Value就是依靠的内部ValueResolver }
最后的那句addEmbeddedValueResolver就能够关联到对于@Value的处理了
三 SpringBoot中的 PropertySourcesPlaceholderConfigurer
在spring-boot-autoconfigure.jar里的spring.factories中第29行有这么一段
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
@Configuration @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) public class PropertyPlaceholderAutoConfiguration { @Bean @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } }
也就是说他会自动的把 PropertySourcesPlaceholderConfigurer,加到Spring容器中,而不是想过去那样需要在配置文件里写明。
同时在Springboot环境下,也不一定非要指定用户自己的属性配置文件比如dbconfig.properties。而是可以直接写到application.yml,或者application.properties中
这是因为springBoot在启动是会指定initializers,我记得有五个initializer,其中有一个专门负责解析classpath下的application.yml,application.properties和 classpath/config/下的application.yml,application.properties一共四个文件。不是说这四个文件一定要都存在,而是如果存在就把环境变量,jar包的启动参数,连同这四个可能的文件里的属性读取出来,共同构造成environment变量。
而实现了EnvironmentAware接口。
public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware
在Spring容器启动并且在执行自动装配的时候就会调用
AutoConfigurationImportSelector.selectImports
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return StringUtils.toStringArray(configurations); }
所以在springBoot中是可以直接读出来yml中的属性的