前言
本文主要从PropertiesLoaderSupport和PropertyResourceConfigurer出发,聊聊由它衍生出来的一些实用API。(和加载Properties有关)
Spring的PropertyResourceConfigurer是个抽象类,继承自PropertiesLoaderSupport,并实现了接口BeanFactoryPostProcessor。
此处注意:它是个Bean工厂的后置处理器,而不是Bean的后置处理器
它抽象了容器启动时,BeanFactory后置处理阶段对容器中所有bean定义中的属性进行配置的一般逻辑,属性配置所使用的属性来源是基类PropertiesLoaderSupport方法所规定的那些属性。
从命名中就能看出,它和PropertyResource有关,且和Bean工厂相关~~
PropertiesLoaderSupport
org.springframework.core.io.support.PropertiesLoaderSupport是一个抽象基类,它抽象了从不同渠道加载属性的通用逻辑,以及这些属性应用优先级上的一些考虑,它所提供的这些功能主要供实现子类使用。
它将属性分成两类:
- 本地属性(也叫缺省属性):直接以Properties对象形式设置进来的属性
- 外来属性:通过外部资源Resource形式设置进来需要加载的那些属性
对于本地属性和外来属性之间的的使用优先级,通过属性localOverride来标识。如果localOverride为false,表示外部属性优先级高,这也是缺省设置。如果localOverride为true,表示本地属性优先级高。
它还有一个属性fileEncoding用来表示从属性文件加载属性时使用的字符集。(毕竟Properties文件存在乱码问题)
// @since 1.2.2 public abstract class PropertiesLoaderSupport { @Nullable protected Properties[] localProperties; protected boolean localOverride = false; // 外部配置 Resource[] @Nullable private Resource[] locations; private boolean ignoreResourceNotFound = false; @Nullable private String fileEncoding; // 从流中读取 Properties / xml // 外来属性加载工具 private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister(); public void setProperties(Properties properties) { this.localProperties = new Properties[] {properties}; } public void setPropertiesArray(Properties... propertiesArray) { this.localProperties = propertiesArray; } public void setLocation(Resource location) { this.locations = new Resource[] {location}; } public void setLocations(Resource... locations) { this.locations = locations; } public void setLocations(Resource... locations) { this.locations = locations; } ... // 省略get/set // 下面是提供给子类实现的方法们~~~~~~~~~ protected Properties mergeProperties() throws IOException { Properties result = new Properties(); // localOverride默认是false 若是true,提前先加载外部化配置 if (this.localOverride) { // Load properties from file upfront, to let local properties override. loadProperties(result); } if (this.localProperties != null) { for (Properties localProp : this.localProperties) { // 厉害了 属性合并 把Properties合并进map localProp本地会覆盖掉后面的result~~ CollectionUtils.mergePropertiesIntoMap(localProp, result); } } // 若是false,再装载一次 把它放进result里面~~~外部配置覆盖当前的result嘛~ if (!this.localOverride) { // Load properties from file afterwards, to let those properties override. loadProperties(result); } return result; } // 加载外部配置~~~~~~~ 从Resource读取进来~ 借助的还是PropertiesLoaderUtils protected void loadProperties(Properties props) throws IOException { if (this.locations != null) { for (Resource location : this.locations) { PropertiesLoaderUtils.fillProperties(props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister); } } } }
对于该类的相关属性,都提供了对应的set方法。
PropertiesLoaderSupport所实现的功能并不多,主要是设置要使用的本地属性和外部属性文件资源路径,最终通过mergeProperties方法将这些属性合并成一个Properties对象,本地属性和外部属性之间的优先级关系由属性localOverride决定。
PropertiesLoaderSupport的直接实现子类有PropertiesFactoryBean和PropertyResourceConfigurer
PropertiesFactoryBean
实现了FactoryBean,用来生产Properties,可以配置是否单例(默认是单例)。
public class PropertiesFactoryBean extends PropertiesLoaderSupport implements FactoryBean<Properties>, InitializingBean { // 默认它是单例的 private boolean singleton = true; @Nullable private Properties singletonInstance; public final void setSingleton(boolean singleton) { this.singleton = singleton; } ... // 只有singleton为true时候,才会创建一个缓存着~~~ @Override public final void afterPropertiesSet() throws IOException { if (this.singleton) { this.singletonInstance = createProperties(); } } // 通过合并本地属性 来得到一个Properties~~~~ protected Properties createProperties() throws IOException { return mergeProperties(); } @Override @Nullable public final Properties getObject() throws IOException { if (this.singleton) { return this.singletonInstance; } else { return createProperties(); } } @Override public Class<Properties> getObjectType() { return Properties.class; } }
在我们还是xml时代的时候,我们其中一种导入配置文件的方式如下(这种代码有木有一种遗失的美好的感觉):
<bean id="prop" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"><!-- 这里是PropertiesFactoryBean类,它也有个locations属性,也是接收一个数组,跟上面一样 <array> <value>classpath:jdbc.properties</value> </array> </property> </bean>
此处直接是向容器内放入了一个Bean,而这个Bean就是Properties类型,显然这种方式并不会加入到环境Environment里面去。so我们在使用@Value引用的时候比如使用SpEL才能引用到:
@Value("#{prop['datasource.url']}") // 用@Value("${datasource.url}") 这样是读取不到的 此处务必要注意
附:其实xml时代还有一种常见的引用配置文件的方式如下:(Spring加载properties文件的两种方式)
<context:property-placeholder location="classpath:jdbc.properties"/>
它的原理其实是PropertyPlaceholderConfigurer,下面会说到。它等价于下面这么配置:
<!-- 与上面的配置等价,下面的更容易理解 这个beanName写不写无所谓 --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <!-- PropertyPlaceholderConfigurer类中有个locations属性,接收的是一个数组,即我们可以在下面配好多个properties文件 --> <array> <value>classpath:jdbc.properties</value> </array> </property> </bean>
这种方式虽然也并没有加入到Environment
环境抽象里面去,但我们取值仍然可以如下取值:(至于原因,小伙伴们可自行思考)
@Value("${datasource.url}") // 占位符取值即可
下面以Java配置方式示例,使用PropertiesFactoryBean
加载属性配置文件:
@Configuration public class RootConfig { @Bean public PropertiesFactoryBean prop() { PropertiesFactoryBean prop = new PropertiesFactoryBean(); prop.setLocation(new ClassPathResource("jdbc.properties")); return prop; } } @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {RootConfig.class}) public class TestSpringBean { @Value("#{prop['datasource.url']}") private String url; @Test public void test1() { System.out.println(url); //jdbc:mysql://localhost:3306/jedi?zeroDateTimeBehavior=convertToNull } }
测试通过,输出正常。
PropertyResourceConfigurer
允许从属性资源(即属性文件)配置单个bean属性值。对于以系统管理员为目标的自定义配置文件很有用,这些文件覆盖在应用程序上下文中配置的bean属性。
它是个抽象类,它的继承图谱如下:
它实现了BeanFactoryPostProcessor并且还实现了PriorityOrdered表示它的优先级是非常高的。
// @since 02.10.2003 public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport implements BeanFactoryPostProcessor, PriorityOrdered { private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered // 处理每个Bean~~~ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { try { // 合并本地属性和外部指定的属性文件资源中的属性 Properties mergedProps = mergeProperties(); // 将属性的值做转换(仅在必要的时候做) convertProperties(mergedProps); // 对容器中的每个bean定义进行处理,也就是替换每个bean定义中的属性中的占位符 // 该方法为抽象方法,子类去处理~~~ processProperties(beanFactory, mergedProps); } catch (IOException ex) { throw new BeanInitializationException("Could not load properties", ex); } } protected void convertProperties(Properties props) { Enumeration<?> propertyNames = props.propertyNames(); while (propertyNames.hasMoreElements()) { String propertyName = (String) propertyNames.nextElement(); String propertyValue = props.getProperty(propertyName); // convertProperty是个空实现,子类不复写就不会有转换的动作~~~~ String convertedValue = convertProperty(propertyName, propertyValue); // 转成功了 说明就不会相等了 那就set进去覆盖之前的~~ if (!ObjectUtils.nullSafeEquals(propertyValue, convertedValue)) { props.setProperty(propertyName, convertedValue); } } } }
processProperties是抽象方法,留给子类去处理占位符等情况。不过其目的很明确,是对容器中每个bean定义中的属性进行处理。但具体处理是什么,就要看实现子类自身的设计目的了。
比如实现子类PropertyOverrideConfigurer和实现子类PropertyPlaceholderConfigurer就分别有自己的bean定义属性处理逻辑。