PropertyPlaceholderConfigurer
PropertyPlaceholderConfigurer
Spirng在生命周期里关于Bean的处理大概可以分为下面几步:
- 加载 Bean 定义(从xml或者从@Import等)
- 处理 BeanFactoryPostProcessor
- 实例化 Bean
- 处理 Bean 的 property 注入
- 处理 BeanPostProcessor
而当我们在声明了
<context:property-placeholder location="classpath:config.properties"/>
标签之后,即声明了一个配置型 bean 交给 Spring 容器进行管理,即 PropertyPlaceholderConfigurer 类。我们先看一下这个类的继承结构。
这里 PrepertyPlaceholderConfigurer 实现了 BeanFactoryPostProcesser 接口,并实现了 postProcessBeanFactory() 方法,即当 Spring 容器的 BeanFactory 被构造成功之后会调用这个方法。这时,我们先看父类的 PropertyResourceConfigurer 方法 postProcessBeanFactory。因为这个类继承了 Spring 的 BeanFactoryPostProcesser 接口,所以这个方法一定是操作 BeanFactory 的。
org.springframework.beans.factory.config.PropertyResourceConfigurer
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
//1. 获取当前容器配置的所有Properties文件,可能由多个文件merge而来
Properties mergedProps = mergeProperties();
//2. 如果需要的话,将Properties文件的内容进行转化,因为默认的Preperties都是String的key-value形式。
// Spring提供的默认方式是不转化,保持String,String的key-value
convertProperties(mergedProps);
//3. 由子类继承,对容器与Properties进行操作,即value注入。
processProperties(beanFactory, mergedProps);
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
这里最重要的第一步就是获得 Properties 文件即 mergeProperties 方法,这是解析资源文件最基本的方法,所以这个方法一定存在于当前功能的最基类中,即 PropertiesLoaderSupport。由于xml中是这样配置的:
<context:property-placeholder location="classpath:config.properties"/>
这里声明了一个 PropertyPlaceholderConfigurer 对象,显然是调用了 setLocation 方法,而这个方法同样存在于该功能模块的最基本父类 PropertiesLoaderSupport 中。
org.springframework.core.io.support.PropertiesLoaderSupport
public abstract class PropertiesLoaderSupport {
private Resource[] locations;
public void setLocation(Resource location) {
this.locations = new Resource[] {location};
}
//注意:后声明的文件覆盖先声明的文件,以最后一个文件为准
public void setLocations(Resource... locations) {
this.locations = locations;
}
}
mergeProperties 方法中进行了配置化管理,将从 this.locations 中加载的 Properties 与 localProperties 合并,localOverride 控制覆盖顺序:
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;
}
// 加载配制文件到 Properties 中
protected void loadProperties(Properties props) throws IOException {
if (this.locations != null) {
//1.遍历声明的Resource文件地址
for (Resource location : this.locations) {
if (logger.isInfoEnabled()) {
logger.info("Loading properties file from " + location);
}
try {
//2.获得Resource文件流,并加载内容到Properties对象中
PropertiesLoaderUtils.fillProperties(
props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
}
catch (IOException ex) {
if (this.ignoreResourceNotFound) {
if (logger.isWarnEnabled()) {
logger.warn("Could not load properties from " + location + ": " + ex.getMessage());
}
}
else {
throw ex;
}
}
}
}
}
回想 PropertyResourceConfigurer 主流程中的三个方法,第一步已经执行完毕,加载了配置的 properties 文件,第二步是 spring 自己的默认实现,将非空的 key 对应的 value 放入 Properties 中,第三步则该由子类各自实现了,将 BeanFactory 与 Properties 进行统一操作。这时候我们看我们直接声明的派生类 PropertyPlaceholderConfigurer。
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
throws BeansException {
//1.声明一个支持value为String类型的Resolver
StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
//2.将key-value注入到BeanFactory的某些bean中
doProcessProperties(beanFactoryToProcess, valueResolver);
}
接下来就是真正的 value 注入环节了
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
//1. 将key-value内容声明为BeanDefinitionVisitor对象,用来根据BeanDefinition修改即将生成的对应的Bean内容
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
for (String curName : beanNames) {
//2. 只有同一个容器内的才可以进行value注入,同时应该避免掉操作本身,避免进入循环递归
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);
}
}
}
//3.处理一些拥有别名的类
beanFactoryToProcess.resolveAliases(valueResolver);
//4.New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.(这一步有些不懂,以后再修正)
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
在上述代码中,第2步已经修改了原始的 BeanDefinition,我们一路跟进去看,原来核心的替换功能在 PropertyPlaceholderHelper 中:
protected String parseStringValue(
String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
//1. 对每一个key进行处理
StringBuilder result = new StringBuilder(strVal);
//2. 首先考虑有占位符的情况,默认是${}
int startIndex = strVal.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
// 考虑 key 占位符嵌套 ${${${}}},先查找外层 ${} 成对出现的最后一个 '}'
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
//3. 如果 key 有占位符,即 key=${abc},则递归调用本方法查找 key 的真实值
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
//4. 真正的从 key-value 集合中获得 key 对应的真实值
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
//5. 如果没有找到,则试图按照 ${key:default} 的形式解析
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
//5.1 获得:之前的内容,即真正的key
String actualPlaceholder = placeholder.substring(0, separatorIndex);
//5.2 获得:之后的内容,即默认值
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
//5.3 再次尝试从key-value集合中获得内容,因为如果真的是key-value的形式,按照全名是肯定找不到的
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
//5.4 如果找到了就按照配置的走,如果没有找到则附上默认值
if (propVal == null) {
propVal = defaultValue;
}
}
}
//6. 如果最终解析到 propVal,则还要判断 propVal 是否有占位符,即 propVal=${} 的情况
if (propVal != null) {
//6.1 如果找到了这个value,则再次递归调用自己,避免value也是占位符的情况
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
//6.2 将获得的结果替换掉
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in string value \"" + strVal + "\"");
}
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
return result.toString();
}
参考:
https://www.cnblogs.com/kingszelda/p/7261156.html
http://blog.csdn.net/qq_28580959/article/details/60129329
PropertyPlaceholderConfigurer的更多相关文章
- PropertiesFactoryBean PropertyPlaceholderConfigurer 区别
正如 stackoverflow上说的,PropertiesFactoryBean 是PropertiesLoaderSupport 直接的实现类, 专门用来管理properties文件的工厂bean ...
- Spring PropertyPlaceholderConfigurer数据库配置
pom.xml中添加依赖 <!-- mysql-connector-java --> <dependency> <groupId>mysql</groupId ...
- spring的多个PropertyPlaceholderConfigurer实例装配的问题
1. 默认情况下,使用PropertyPlaceholderConfigurer多实例装配出现异常 在项目中尝试 在不同的spring的配置文件中分别引入相应的properties文件,这样会在spr ...
- Spring里PropertyPlaceholderConfigurer类的使用
1. PropertyPlaceholderConfigurer是个bean工厂后置处理器的实现,也就是 BeanFactoryPostProcessor接口的一个实现.PropertyPlaceho ...
- 读取配置文件 PropertyPlaceholderConfigurer 的配置与使用
public class SpringPropertyConfigurer extends PropertyPlaceholderConfigurer { private static Map< ...
- PropertyPlaceholderConfigurer的用法:
用法1: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://w ...
- Spring中PropertyPlaceholderConfigurer的使用
Spring中PropertyPlaceholderConfigurer的使用 在使用Spring配置获取properties文件时,在网上查到相关的资料,分享哈!! (1)获取一个配置文件 ...
- spring PropertyPlaceholderConfigurer 找不到配置文件原因
1: spring 版本问题 参见: http://www.cnblogs.com/alex-blog/archive/2012/12/25/2832357.html 2: bean id 同名 ...
- PropertyPlaceholderConfigurer加载属性配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...
- spring中propertyplaceholderconfigurer简介
Spring的框架中为您提供了一个 BeanFactoryPostProcessor 的实作类别: org.springframework.beans.factory.config.PropertyP ...
随机推荐
- SpringMVC Controller 单例 多例
对于SpringMVC 的Controller单例还是多例.下面举例说明:第一次:类是多例,类里包含一个普通属性,一个静态属性 结果:普通属性:0.............静态属性:0 普通属性:0. ...
- 判断元素16种方法expected_conditions
前言 标签(空格分隔): 判断元素 经常有小伙伴问,如何判断一个元素是否存在,如何判断alert弹窗出来了,如何判断动态的元素等等一系列的判断,在selenium的expected_condition ...
- python学习day5 常量 运算符补充 流程控制基础
1.常量 值不会改变的量 python中没有特别的语法定义常量,一般约定用全大写字母命名常量,比如圆周率pi 2.预算符补充 2.1算数运算符 print(10/3)除法运算 print(10//3) ...
- MongoDB之$关键字及$修改器$set $inc $push $pull $pop
一.查询中常见的 等于 大于 小于 大于等于 小于等于 等于:用':' 大于:用'$gt' 小于:用'$lt' 大于等于:用'$gte' 小于等于:用'$lte' MongoDB的操作就是 ...
- @RequestMapping 和 @GetMapping @PostMapping 区别
@RequestMapping 和 @GetMapping @PostMapping 区别 @GetMapping是一个组合注解,是@RequestMapping(method = Requ ...
- REUSE_ALV_FIELDCATALOG_MERGE
作用: 根据程序中的数据内表结构,来自动生成FIELDCAT[]内表,不用定义宏或者Form来一个个加入,会根据内表结构所参照的词典类型来自动完成如表标题字段名的生成,得到大概的FIELDCAT[]后 ...
- [leetcode]270. Closest Binary Search Tree Value二叉搜索树中找target的最接近值
Given a non-empty binary search tree and a target value, find the value in the BST that is closest t ...
- shelve
shelve是对pickle的封装 json & pickle是把所有的数据全部封装,一次性写入文件,而shelve可以把数据分类,以键值对的形式分别写入文件 shelve模块是一个简单的k, ...
- day 21 封装,多态,类的其他属性
封装 封装:将一些数据,重要的信息等等放到一个地方(空间中) class A: country = 'China' area = '深圳' def __init__(self,name,age): s ...
- JDBC的学习
JDBC —— 用Java访问数据库 一.需要用到第三方类:mysql-connector-java-5.0.8-bin.jar,并做好导包处理: 二.初始化驱动: 三.建立与数据库的链接: 四.创建 ...