当前位置: 首页 > news >正文

Spring属性配置解析机制详解

PropertyPlaceholderConfigurer

PropertyPlaceholderConfigurer

img

Spirng在生命周期里关于Bean的处理大概可以分为下面几步:

  1. 加载 Bean 定义(从xml或者从@Import等)
  2. 处理 BeanFactoryPostProcessor
  3. 实例化 Bean
  4. 处理 Bean 的 property 注入
  5. 处理 BeanPostProcessor

而当我们在声明了

<context:property-placeholder location="classpath:config.properties"/>

标签之后,即声明了一个配置型 bean 交给 Spring 容器进行管理,即 PropertyPlaceholderConfigurer 类。我们先看一下这个类的继承结构。
img

这里 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-valueconvertProperties(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类型的ResolverStringValueResolver 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 获得:之前的内容,即真正的keyString 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

http://www.dtcms.com/a/404170.html

相关文章:

  • dede视频网站源码网站开发 语言
  • Python Web 开发入门指南
  • 9.21记录
  • 传媒公司做网站条件公司网站优化外包
  • 仓颉编程语言青少年基础教程:异常处理
  • 政务网站建设及管理搜索引擎优化的名词解释
  • AP2协议与AI Agent经济:重塑金融生态与主权国家挑战
  • 机器学习之损失函数
  • 语义三角论对人工智能自然语言处理的深层语义分析的影响与启示
  • 佛山市住房和城乡建设部网站姓氏变logo设计免费生成
  • DOS与DDOS攻击防御详解
  • 【Git】基本概念 + 基操
  • 建设优化网站wordpress侧导航菜单
  • 网站建设项目执行情况报告模板手机wap网站下载
  • 【笔试强训】Day01
  • 网站的想法夫唯seo
  • 阿里云建站套餐旅游网站排行榜前十名官网
  • 上饶网站开发 app开发做移动网站点击软件
  • 常州微信网站建设流程北京免费模板建站
  • AKS论文阅读
  • 快捷的赣州网站建设网站开发的逻辑
  • 【图论】【数据结构】图的深度优先与广度优先遍历、最短路径
  • 制作网站作业wordpress设置关键字
  • 网站建设 seo模块泉州企业网站维护定制
  • 如何做音乐分享类网站个人网页制作模板田田田田田田田田
  • 网站建设需求说明书怎么写做婚礼效果图的网站有哪些
  • 温州免费建站私密浏览器视频
  • 速通ACM省铜第十四天 赋源码(Coloring Game)
  • 淮安做网站seo海南省建设注册执业资格中心网站
  • 阿里云 PAI 携手 NVIDIA 提供端到端物理 AI 解决方案