【spring源码系列-03】xml配置文件启动spring时refresh的前置工作

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 【spring源码系列-03】xml配置文件启动spring时refresh的前置工作

一,xml配置文件启动spring时refresh的前置工作

前两篇大概的描述了一下springIoc的整体流程,接下来再对里面的细节进行分析。如下依旧是通过经典的xml的方式获取到上下文,并且在resources目录下配置一个spring.xml文件,这里推荐使用debug的方式,从上往下看

ApplicationContext ioc=new ClassPathXmlApplicationContext("classpath:spring.xml");

进入这个获取上下文的构造方法之后,可以发现有调用了这个this方法

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
  this(new String[] {configLocation}, true, null);
}

接下来在进入这个this方法,就是一个熟悉的方法,该方法在前两篇中有所提到。接下来重点就是对里面的前两个方法进行深究,弄清refresh的前置工作到底做了什么

public ClassPathXmlApplicationContext(
  String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
  super(parent); // 初始化父类 ,获得xml路径资源解析器
  setConfigLocations(configLocations); // 通过环境变量解析 xml路径
  if (refresh) {
    refresh(); // 这个方法时spring是最终要的一个方法,甚至体系整个ioc的声明周期
  }
}

1,super(parent)

在该方法中,第一步就是初始化父类,后面很多需要使用的对象,就是在这一步被创建的,而里面的super继续调用自己的super,直到创建一个资源模式处理器,该 AbstractApplicationContext 相对来说比较重要,并且那个最重要的refresh 方法就是在这个抽象类里面

public AbstractApplicationContext() {
    //获取资源模式处理器
  this.resourcePatternResolver = getResourcePatternResolver();
}

接下来就是查看这个具体的获取资源处理器的流程,里面的xml文件,或者其他的注解配置文件,都是能获取的资源,获取到资源之后就对资源进行一个解析操作

protected ResourcePatternResolver getResourcePatternResolver() {
  return new PathMatchingResourcePatternResolver(this);
}

接下来在查看这个 PathMatchingResourcePatternResolver 对象,可以发现里面就是获取资源对象加载器。并且里面还存在一个对象PathMatcher,用做于路径匹配

//用于模式匹配,默认使用的是 PathMatcher
private PathMatcher pathMatcher = new AntPathMatcher();
//获取资源加载器
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
  Assert.notNull(resourceLoader, "ResourceLoader must not be null");
  this.resourceLoader = resourceLoader;
}

在这个ResourceLoader 类中,主要就是两个方法,一个是用于加载资源,一个是用于加载类加载器

//加载资源
Resource getResource(String location);
@Nullable
//加载类加载器
ClassLoader getClassLoader();

在这个 AbstractApplicationContext 构造方法中,完成this获取一个资源解析器之后,接下来就是一个设置一个Parent的父类,当前springIOC中是没有父子容器的概念的,因此到后续的springMVC再进行分析

public AbstractApplicationContext(@Nullable ApplicationContext parent) {
  this();
    //springIOC中暂时没有父子容器概念,先跳过
  setParent(parent);
}

因此这一整个步骤,都是为了初始化成员变量。而最主要的,就是初始化一个资源的解析器。

2,setConfigLocations()

在获取到这个资源解析器之后,接下来就是设置文件的路径。如在正常开发的springboot项目中,通过设置环境的的属性来表名是dev环境还是线上环境等。这个locations参数就是 new String[] {configLocation}

//参数可以是对象或者数组
public void setConfigLocations(@Nullable String... locations) {
  if (locations != null) {
    Assert.noNullElements(locations, "Config locations must not be null");
    this.configLocations = new String[locations.length];
    for (int i = 0; i < locations.length; i++) {
      this.configLocations[i] = resolvePath(locations[i]).trim();
    }
  }
  else {
    this.configLocations = null;
  }
}

2.1,获取系统属性和系统环境

在获取到外部传进来的文件路径之后,接下来会通过这个 resolvePath方法解析这个路径。而在解析这个路径时,需要通过系统环境变量来解析,如果环境变量为空,则创建一个标准的环境变量

protected String resolvePath(String path) {
    //获取环境
  return getEnvironment().resolveRequiredPlaceholders(path);
}
//如果获取的环境为空,则创建一个标准环境
protected ConfigurableEnvironment createEnvironment() {
  return new StandardEnvironment();
}

而在这些环境中,存在一些spring环境变量的类型,分别是可忽视的,活跃的默认的等

public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";

同时在这个标准环境中,主要分为系统环境和系统属性等

//系统环境属性资源名称
static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
//系统配置变量资源名称
static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

最后将全部的系统环境和系统属性一起加入到 propertySources 这个PropertySources集合中,该集合是在父类中实例化的,因此会作为一个全局共享的资源,其子类都能获取和访问

protected void customizePropertySources(MutablePropertySources propertySources) {
  propertySources.addLast(
      new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
  propertySources.addLast(
      new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

加入到集合的value值主要是系统的变量和系统的环境。

//获取系统的属性值
getSystemProperties(){System.getProperties()};
//获取系统的变量
getSystemEnvironment(){System.getenv()};

在创建这个 StandardEnvironment() 标准的环境的时候,可以在父类的无参构造方法中打一个断点,可以发现此时会有两个属性值,就是上面的系统属性值和系统环境值

而在下面的propertySourceList的第一个值systemProperties中,已经加载了56个系统属性,比如说一些 jdk的版本,虚拟机的版本,操作系统的名称,当前用户的名称等等


在下面的propertySourceList的第二个值systemEnvironment中,也有49个值,比如说当前电脑的名称,使用的maven路径以及版本,java_home的路径,用户的用户名等等

此时这些默认的环境对象和环境变量就全被获取。当然这些环境变量的数量也可能因为源码的版本不同个数也会不同。

2.2,解析系统环境和系统属性

又回到上面的第二步,此时环境变量值依旧获取,因此接下来就继续执行这个 resolveRequiredPlaceholders 方法

protected String resolvePath(String path) {
    //获取环境
  return getEnvironment().resolveRequiredPlaceholders(path);
}

在resolveRequiredPlaceholders方法中,会获取到刚刚全部获取到的环境和属性,然后对这些环境和属性做一个解析操作。这里的话类似于一个责任链模式,系统环境要处理的会有对应的方法处理系统环境,系统属性要处理的会有对应的方法处理系统属性。下面这个是处理系统环境的方法

@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    //this.propertyResolver:全部的系统环境和系统属性
  return this.propertyResolver.resolveRequiredPlaceholders(text);
}
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
  if (this.strictHelper == null) {
    this.strictHelper = createPlaceholderHelper(false);
  }
    //解析工作
  return doResolvePlaceholders(text, this.strictHelper);
}

而在处理系统属性时,会有一个 createPlaceholderHelper 方法,类似于一个builder的工厂类

private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
    //前缀,后缀
  return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
    this.valueSeparator, ignoreUnresolvablePlaceholders);
}

在获取到这个strictHelper 对象之后,接下来开始真正的进行解析工作

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
  return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}

再次进入这个replacePlaceholders 这个方法,可以发现里面会有一个重要的方法parseStringValue

public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
  Assert.notNull(value, "'value' must not be null");
  return parseStringValue(value, placeholderResolver, new HashSet < > ());
}

接下来查看这个 parseStringValue 方法,首先会判断当前的value值中是否包含一个 $ 的大括号,并且会递归的判断是否存在$ 的嵌套,判断完成之后,会对里面的值进行解析。


在获取完这个符之后,接着就是递归的循环遍历资源中的 k e y 值,将 < c o d e > 符之后,接着就是递归的循环遍历资源中的key值,将<code>符之后,接着就是递归的循环遍历资源中的key值,将<code>{USERNAME} 对应的值进行一个替换操作。

String propVal = placeholderResolver.resolvePlaceholder(placeholder);

再次跟着debug断点走,可以发现会进入 PropertySourcesPropertyResolver 类的 getProperty 方法里面


里面进行循环的取值,将${}里面的值和系统属性或者系统变量的值进行匹配,如果匹配成功,则进行替换的操作

@Nullable
protected < T > T getProperty(String key, Class < T > targetValueType, boolean resolveNestedPlaceholders) {
  if (this.propertySources != null) {
    for (PropertySource << ? > propertySource : this.propertySources) {
            //取值
      Object value = propertySource.getProperty(key);
      if (value != null) {
                //取值成功,则进行替换操作
        if (resolveNestedPlaceholders && value instanceof String) {
          value = resolveNestedPlaceholders((String) value);
        }
        logKeyFound(key, propertySource, value);
                //如果需要的换则进行值转换
        return convertValueIfNecessary(value, targetValueType);
      }
    }
  }
  return null;
}

接下来再进入转换的convertValueIfNecessary 方法,如果不需要转换则直接返回,需要转换则转换

@Nullable
protected < T > T convertValueIfNecessary(Object value, @Nullable Class < T > targetType) {
  if (targetType == null) {
    return (T) value;
  }
  ConversionService conversionServiceToUse = this.conversionService;
  if (conversionServiceToUse == null) {
    if (ClassUtils.isAssignableValue(targetType, value)) {
      return (T) value;
    }
    conversionServiceToUse = DefaultConversionService.getSharedInstance();
  }
    //转换
  return conversionServiceToUse.convert(value, targetType);
}

如已知刚刚获取到的系统环境变量中存在一个 USERNAME=‘PV’,那么假设xml的文件名为 spring-$ {USERNAME}.xml ,那么结果这个解析器进行解析之后,就会将这个${}里面的值进行一个替换操作,会将这个文件名变成 spring-PV.xml 文件。如果存在$的嵌套,那么就会递归的进行一个判断和替换操作, 最终会将解析后的文件返回。


自此为止,属性值就全部加载和解析完成。此时所有的配置文件路径等,都添加在重要的类AbstractRefreshableConfigApplicationContext的configLocations 的属性里面。

private String[] configLocations;

除了刚刚举例,还有像一些jdbc的连接参数等等,其原理都是一样的,都是通过这种方式替换

"${jdbc.url}")
"${jdbc.driverClassName}"
"${jdbc.username}"
"${jdbc.password}"

3,总结

也就是通过这个xml的方式作为配置文件,在调用refresh方法之前,主要就是做了两件事情:首先是初始化一个资源的解析器,随后是获取系统的属性和系统的环境变量,同时对配置文件的路径进行解析。至此为止,refresh需要准备的前戏工作结束。

相关文章
|
10天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
39 2
|
1月前
|
XML Java 数据格式
Spring从入门到入土(xml配置文件的基础使用方式)
本文详细介绍了Spring框架中XML配置文件的使用方法,包括读取配置文件、创建带参数的构造对象、使用工厂方法和静态方法创建对象、对象生命周期管理以及单例和多例模式的测试。
93 7
Spring从入门到入土(xml配置文件的基础使用方式)
|
27天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
29天前
|
Java API Spring
在 Spring 配置文件中配置 Filter 的步骤
【10月更文挑战第21天】在 Spring 配置文件中配置 Filter 是实现请求过滤的重要手段。通过合理的配置,可以灵活地对请求进行处理,满足各种应用需求。还可以根据具体的项目要求和实际情况,进一步深入研究和优化 Filter 的配置,以提高应用的性能和安全性。
|
16天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
40 9
|
1月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
113 5
|
1月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
1月前
|
XML Java 应用服务中间件
tomcat学习一:tomcat 目录及配置文件学习 server.xml 等
这篇文章是关于Apache Tomcat服务器的目录结构、配置文件(特别是server.xml)的详细介绍和学习指南。
64 0
tomcat学习一:tomcat 目录及配置文件学习 server.xml 等
|
1月前
|
Java 测试技术 Spring
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
这篇文章介绍了Spring Boot中配置文件的语法、如何读取配置文件以及如何通过静态工具类读取配置文件。
61 0
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
|
1月前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
47 0
下一篇
无影云桌面