spring是如何解析xml配置文件中的占位符

前言

我们在配置Spring Xml配置文件的时候,可以在文件路径字符串中加入 ${} 占位符,Spring会自动帮我们解析占位符,这么神奇的操作Spring是怎么帮我们完成的呢?这篇文章我们就来一步步揭秘。

1.示例

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
applicationContext.setConfigLocation("${java.version}.xml");
applicationContext.refresh();
String[] beanNames = applicationContext.getBeanDefinitionNames();
for (String beanName : beanNames) {
 System.out.println(beanName);
}

这段代码在我工程里是会报错的,如下:

Caused by: java.io.FileNotFoundException: class path resource [1.8.0_144.xml] cannot be opened because it does not exist
 at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:190)
 at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336)
 ... 11 more

可以看到报错里面的文件路径变成了1.8.0_144.xml,也就是说Spring帮我们把${java.version}解析成了实际值。

2.原理

AbstractRefreshableConfigApplicationContext
我们在之前的文章里提到过这个类的resolve方法,我们再来瞧一眼:

/**
  * Resolve the given path, replacing placeholders with corresponding
  * environment property values if necessary. Applied to config locations.
  * @param path the original file path
  * @return the resolved file path
  * @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)
  */
 protected String resolvePath(String path) {
  //通过当前环境去 解析 必要的占位符
  return getEnvironment().resolveRequiredPlaceholders(path);
 }

获取当前环境,这个环境在示例代码中就是 StandardEnvironment ,并且根据当前环境去解析占位符,这个占位符解析不到还会报错。

resolveRequiredPlaceHolders由StandardEnvironment的父类AbstractEnvironment实现。

AbstractEnvironment

//把propertySources放入 Resolver中
private final ConfigurablePropertyResolver propertyResolver =
   new PropertySourcesPropertyResolver(this.propertySources);
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
 return this.propertyResolver.resolveRequiredPlaceholders(text);
}

这里的propertySources很重要了,从命名也可以看出我们解析占位符的来源就是从这个集合中来的。这个集合是在我们StandardEnvironment实例化的时候去自定义的。

StandardEnvironment

/**
  * Create a new {@code Environment} instance, calling back to
  * {@link #customizePropertySources(MutablePropertySources)} during construction to
  * allow subclasses to contribute or manipulate(操作) {@link PropertySource} instances as
  * appropriate.
  * @see #customizePropertySources(MutablePropertySources)
  */
 //StandardEnvironment 实例化调用
 public AbstractEnvironment() {
  customizePropertySources(this.propertySources);
 }
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {

 //todo Java提供了System类的静态方法getenv()和getProperty()用于返回系统相关的变量与属性,
 //todo getenv方法返回的变量大多于系统相关,
 //todo getProperty方法返回的变量大多与java程序有关。
 //https://www.cnblogs.com/Baronboy/p/6030443.html
 propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));

 //SystemEnvironmentPropertySource 是System.getenv()
 propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

最重要的肯定是我们的 propertyResolver.resolveRequiredPlaceholders 方法了,propertyResolver.resolveRequiredPlaceholders其实是PropertySourcesPropertyResolver的父类AbstractPropertyResolver来实现。

AbstractPropertyResolver

//创建一个占位符的helper去解析
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
 if (this.strictHelper == null) {
  //不忽略
  this.strictHelper = createPlaceholderHelper(false);
 }
 return doResolvePlaceholders(text, this.strictHelper);
}
 //私有方法
 //是否忽略 无法解决的占位符
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {

  //默认使用${ placeholderPrefix
  return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
    this.valueSeparator, ignoreUnresolvablePlaceholders);
}
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {

  //PlaceholderResolver function interface
  //todo important 重要的是这个getPropertyAsRawString
  return helper.replacePlaceholders(text, this::getPropertyAsRawString);
 }

这里的 this::getPropertyAsRawString 很重要,利用了java8的函数式接口来实现。它的定义在AbstractPropertyResolver里

/**
  * Retrieve the specified property as a raw String,
  * i.e. without resolution of nested placeholders.
  * @param key the property name to resolve
  * @return the property value or {@code null} if none found
  */
 @Nullable
 protected abstract String getPropertyAsRawString(String key);

但是我们在doResolvePlaceholders里指向的this,所以还得看PropertySourcesPropertyResolver类。

PropertySourcesPropertyResolver

//提供给函数接口 PlaceholderResolver
 //todo 解析 xml配置文件路径占位符的时候调用的是这个 2020-09-11
 @Override
 @Nullable
 protected String getPropertyAsRawString(String key) {
  return getProperty(key, String.class, false);
 }
@Nullable
 protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
  if (this.propertySources != null) {

   //例如遍历的是MutablePropertySources 的propertySourceList
   for (PropertySource<?> propertySource : this.propertySources) {
    if (logger.isTraceEnabled()) {
     logger.trace("Searching for key '" + key + "' in PropertySource '" +
       propertySource.getName() + "'");
    }
    Object value = propertySource.getProperty(key);
    if (value != null) {
     //todo 解析 profile变量的时候 会去 解析 变量中的占位符 2020-09-11
     //TODO 解析xml配置文件路径字符串的时候 如果占位符 变量 的值 包含占位符 在这里 不会去解析 通过Helper 去解析 PropertyPlaceholderHelper
     if (resolveNestedPlaceholders && value instanceof String) {
      value = resolveNestedPlaceholders((String) value);
     }
     logKeyFound(key, propertySource, value);
     //跳出for 循环
     return convertValueIfNecessary(value, targetValueType);
    }
   }
  }
  if (logger.isTraceEnabled()) {
   logger.trace("Could not find key '" + key + "' in any property source");
  }
  return null;
 }

看到没有,我们是遍历this.propertySources集合,然后根据key调用它的getProperty方法获取value。我们从上面的StandardEnvrionment中看到我们定义的是 MapPropertySource 和 SystemEnvironmentPropertySource .

MapPropertySource

//从source中取得属性
@Override
@Nullable
public Object getProperty(String name) {
 return this.source.get(name);
}

这里的source就是getSystemProperties(),也就是 AbstractEnvironment中的方法:

@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Map<String, Object> getSystemProperties() {
 try {
  //Hashtable
  return (Map) System.getProperties();
 }
 catch (AccessControlException ex) {
  return (Map) new ReadOnlySystemAttributesMap() {
   @Override
   @Nullable
   protected String getSystemAttribute(String attributeName) {
   try {
    return System.getProperty(attributeName);
   }
   catch (AccessControlException ex) {
    if (logger.isInfoEnabled()) {
     logger.info("Caught AccessControlException when accessing system property '" +
      attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
    }
    return null;
   }
   }
  };
 }
}

我们还忘了很重要的一步,就是PropertyPlaceholderHelper的replacePlaceholders方法。

PropertyPlaceholderHelper

//protected 范围
protected String parseStringValue(
  String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

 StringBuilder result = new StringBuilder(value);

 //如果value中没有占位符前缀 那直接返回result
 int startIndex = value.indexOf(this.placeholderPrefix);
 while (startIndex != -1) {
  //找到占位符的最后一个索引
  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");
   }

   //1. todo 2020-09-01 解析出来占位符,比如java.version
   //解析内嵌占位符
   // Recursive invocation, parsing placeholders contained in the placeholder key.
   placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
   // Now obtain the value for the fully resolved key...
   //2.todo 2020-09-01 获取实际值
   String propVal = placeholderResolver.resolvePlaceholder(placeholder);
   if (propVal == null && this.valueSeparator != null) {
   int separatorIndex = placeholder.indexOf(this.valueSeparator);
   if (separatorIndex != -1) {
    String actualPlaceholder = placeholder.substring(0, separatorIndex);
    String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
    //这里就是实际获取占位符中值得地方。
    propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);

   }
   }
  if (propVal != null) {
     //从占位符里获取的值也有可能包含占位符 这里可能会报 Circular placeholder reference
     propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
     //替换占位符 为 实际值
      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 {
   startIndex = -1;
  }
 }
 return result.toString();
}

到这里我们就可以看到Spring在处理一个小小的占位符就做了这么多设计。可见这个架构是如此严谨。下篇文章我们就来探讨下Spring是如何加载这个Xml文件的。

以上就是spring是如何解析xml配置文件中的占位符的详细内容,更多关于spring解析xml 占位符的资料请关注我们其它相关文章!

(0)

相关推荐

  • 在Spring Boot中加载XML配置的完整步骤

    开篇 在SpringBoot中我们通常都是基于注解来开发的,实话说其实这个功能比较鸡肋,但是,SpringBoot中还是能做到的.所以用不用是一回事,会不会又是另外一回事. 涛锅锅在个人能力能掌握的范围之内,一般是会得越多越好,都是细小的积累,发生质的改变,所以今天和小伙伴们一起分享一下. 实践 1.首先我们新建一个SpringBoot Project ,工程名为 xml 2.添加web依赖,点击Finish完成构建 3.我们新建一个类 SayHello 不做任何配置 package org.t

  • 如何在spring官网查找XML基础配置文件

    这篇文章主要介绍了如何在spring官网查找XML基础配置文件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.首先进入spring官网:https://spring.io/: 2.然后点击projects目录,出现如下页面: 3.点击spring framework进入spring框架页面,点击Learn,点击Reference Doc如图: 4.进入doc页面后,点击Core,如图: 5.进入core页面后,点击1.2.1 Configu

  • Spring如何自定义XML配置扩展

    在Spring中,我们定义一个自己的标签有如下步骤: 自己定义一个XSD文件. 定义一个和XSD文件所对应的实体类. 创建实现了BeanDefinitionParser的类(其实更好的做法是继承抽象类AbstractBeanDefinitionParser),去解析我们的自定义标签. 创建一个继承了NamespaceHandlerSupport的类,去将我们创建的类注册到spring容器. 编写自己的Spring.handlers和Spring.schemas 一.定义一个XSD文件 首先我们在

  • spring通过导入jar包和配置xml文件启动的步骤详解

    第一步,进到 spring仓库下载一个spring包,大家前往官网下载的时候,记得下载dist.zip后缀的包,里面包括了jar包和对应的英文文档. 下面是自己已经下载的一个,提供百度云链接: 百度云,提取码:hpst 第二步,打开idea -> File -> Project Structrure -> Libraries,点击上面的 "+"号,找到存放spring源码的目录,进入libs目录,然后将 beans.context.core.expression.jc

  • 基于XML配置Spring的自动装配过程解析

    一.了解Spring自动装配的方式 采用传统的XML方式配置Bean组件的关键代码如下所示 <bean id="userMapper" class="edu.cn.dao.UserMapperImpl"> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> <bean id="userSer

  • springboot 在xml里读取yml的配置信息的示例代码

    YML是什么 YAML (YAML Ain't a Markup Language)YAML不是一种标记语言,通常以.yml为后缀的文件,是一种直观的能够被电脑识别的数据序列化格式,并且容易被人类阅读,容易和脚本语言交互的,可以被支持YAML库的不同的编程语言程序导入,一种专门用来写配置文件的语言.可用于如: Java,C/C++, Ruby, Python, Perl, C#, PHP等. 可以用<springProperty> 标签从Spring中显示属性 以下为在日志配置文件中读取的示例

  • Spring中基于XML的AOP配置详解

    1. 准备工作 1.1 创建工程 day03_eesy_03SpringAOP 1.2 在配置文件pom.xml中添加依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  • SpringBoot集成JmsTemplate(队列模式和主题模式)及xml和JavaConfig配置详解

    1.导入jar包: <!--jmsTemplate--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-activemq</artifactId> </dependency> <dependency> <groupId>org.apache.activemq</g

  • spring是如何解析xml配置文件中的占位符

    前言 我们在配置Spring Xml配置文件的时候,可以在文件路径字符串中加入 ${} 占位符,Spring会自动帮我们解析占位符,这么神奇的操作Spring是怎么帮我们完成的呢?这篇文章我们就来一步步揭秘. 1.示例 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(); applicationContext.setConfigLocation("${java.versi

  • Spring boot AOP通过XML配置文件声明

    通过 XML 配置文件声明 在前两篇博文和示例中,我们已经展示了如何通过注解配置去声明切面,下面我们看看如何在 XML 文件中声明切面.下面先列出 XML 中声明 AOP 的常用元素: AOP配置元素 用途 aop:advisor 定义AOP通知器 aop:after 定义AOP后置通知(不管被通知的方法是否执行成功) aop:after-returning 定义AOP返回通知 aop:after-throwing 定义AOP异常通知 aop:around 定义AOP环绕通知 aop:aspec

  • Spring boot AOP通过XML配置文件声明的方法

    通过 XML 配置文件声明 在前两篇博文和示例中,我们已经展示了如何通过注解配置去声明切面,下面我们看看如何在 XML 文件中声明切面.下面先列出 XML 中声明 AOP 的常用元素: AOP配置元素 用途 aop:advisor 定义AOP通知器 aop:after 定义AOP后置通知(不管被通知的方法是否执行成功) aop:after-returning 定义AOP返回通知 aop:after-throwing 定义AOP异常通知 aop:around 定义AOP环绕通知 aop:aspec

  • 实例分析nodejs模块xml2js解析xml过程中遇到的坑

    本文实例讲述了nodejs模块xml2js解析xml过程中遇到的坑.分享给大家供大家参考,具体如下: 在一个项目中,用到nodejs模块xml2js解析xml,xml的数据如下: <xml> <MsgId>6197906553041859764</MsgId> </xml> 用xml2js中的xml2js.parseString 方法解析,本来以为是一个json,但总是解析失败,把解析的结果log下后如下: { xml: { MsgId: [ '619790

  • 如何使用Spring Boot ApplicationRunner解析命令行中的参数

    使用Spring提供的CommandLineRunner接口可以实现了一个命令行应用程序.但是,参数/选项/参数处理却不是那么好.幸运的是,有一种更好的方法可以使用Spring Boot编写命令行应用程序,并且还可以使用ApplicationRunner接口进行解析. 在我们开始快速说明之前.在这两种情况下,无论是CommandLineRunner还是ApplicationRunner,都始终支持Spring的属性处理.我们可以像往常一样使用@Value注释注入值. 完整的工作源代码在这里 首先

  • web.xml中Maven占位符不生效问题记录分析

    目录 问题背景 问题分析 Resources插件有三个目标: 问题定位 问题解决 问题背景 开发反馈,一个spring mvc的web项目,在web.xml配置的占位符不生效,编译后还是没有替换成配置的属性,如下: <context-param> <param-name>logbackConfigLocation</param-name> <param-value>classpath:${loagback.xml.path:logback.xml}</

  • 浅谈python中的占位符

    占位符,顾名思义就是插在输出里站位的符号.我们可以把它理解成我们预定饭店.当我们告诉饭店的时候,饭店的系统里会有我们的预定位置.虽然我们现在没有去但是后来的顾客就排在我们后面. 常见的占位符有三种: 1.%d 整数占位符 >>>'我考了%d分' % 20 '我考了20分' >>>'我考了%d分' % 20.5 ;我考了20分' >>>"我考了%d分,进步了%d分" % (50,10) "我考了50分,进步了10分"

  • java使用dom4j解析xml配置文件实现抽象工厂反射示例

    逻辑描述: 现在我们想在B层和D层加上接口层,并使用工厂.而我们可以将创建B和创建D看作是两个系列,然后就可以使用抽象工厂进行创建了. 配置文件:beans-config.xml.service-class与dao-class分别对应两个系列的产品.子菜单中id对应接口的命名空间,class对应实现类的命名空间. 复制代码 代码如下: [html] view plaincopyprint? <?xml version="1.0" encoding="UTF-8"

  • Spring手动生成web.xml配置文件过程详解

    步骤一: 找到自己所创建的项目名,效果如下: 步骤二: 右击自己所创建的项目---->Java EE Tools---->点击Generate Deployment Descriptor Stub,完成这几步,即可,效果如下: 最后,就会生成web.xml配置文件会在WebContent-->WEB-INF文件中,如下: 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们.

  • 使用@value注解取不到application.xml配置文件中的值问题

    目录 @value注解取不到application.xml的值 报错信息 原来代码 问题原因 思考 拓展阅读 工具类@Value取不到值 原因是new的对象 @value注解取不到application.xml的值 报错信息 今天从码云上拉下来代码,突然发现系统跑不起来了,报错信息如下: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'shiroConfig': Inj

随机推荐