Spring IOC源码:ApplicationContext刷新前准备工作

本文涉及的产品
云解析 DNS,旗舰版 1个月
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
简介: Spring IOC源码:ApplicationContext刷新前准备工作

文章目录

Spring源码系列:

前言

正文

方法1:super(parent);

方法2: AbstractApplicationContext:

方法3: setParent

方法4:setConfigLocations

方法5: resolvePath

方法6:getEnvironment

方法7:createEnvironment

方法8:StandardEnvironment

方法9 resolveRequiredPlaceholders

方法10 resolveRequiredPlaceholders

方法11 doResolvePlaceholders

总结

Spring源码系列:

Spring IOC源码:简单易懂的Spring IOC 思路介绍

Spring IOC源码:核心流程介绍

Spring IOC源码:ApplicationContext刷新前准备工作

Spring IOC源码:obtainFreshBeanFactory 详解(上)

Spring IOC源码:obtainFreshBeanFactory 详解(中)

Spring IOC源码:obtainFreshBeanFactory 详解(下)

Spring IOC源码:<context:component-scan>源码详解

Spring IOC源码:invokeBeanFactoryPostProcessors 后置处理器详解

Spring IOC源码:registerBeanPostProcessors 详解

Spring IOC源码:实例化前的准备工作

Spring IOC源码:finishBeanFactoryInitialization详解

Spring IoC源码:getBean 详解

Spring IoC源码:createBean( 上)

Spring IoC源码:createBean( 中)

Spring IoC源码:createBean( 下)

Spring IoC源码:finishRefresh 完成刷新详解

前言

在进入spring正式核心逻辑处理前,Spring IOC需要提前进行些初始化工作,为后续的操作准备好一些环境。下面主要讲解这部分的内容,也是refresh()方法前的代码逻辑,即下面的super(parent),setConfigLocations(configLocations);本人以xml配置的方式,入口为ClassPathXmlApplicationContext;

  public ClassPathXmlApplicationContext(
      String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
      throws BeansException {
    //调用父类方法进行初始化
    super(parent);
    //对location文件路径进行解析、替换占位符等工作
    setConfigLocations(configLocations);
    if (refresh) {
      refresh();
    }
  }

super(parent); 见方法1详解

setConfigLocations(configLocations); 见方法4详解

正文

方法1:super(parent);

6b7643dacc70adbd489b9feb5b1e6be1_acd39a3e84ed440aa9a39c72b0f0d58a.png

可以看到ClassPathXmlApplicationContext最上面的父类为AbstractApplicationContext,这里一直往上调用直到AbstractApplicationContext父类中进行初始化工作,

  public AbstractApplicationContext(@Nullable ApplicationContext parent) {
    this();
    setParent(parent);
  }

this(); 调用无参构造方法 见方法2

setParent(parent); 设置此上下文的父类 见方法3

方法2: AbstractApplicationContext:

  public AbstractApplicationContext() {
    this.resourcePatternResolver = getResourcePatternResolver();
  }


  protected ResourcePatternResolver getResourcePatternResolver() {
    //返回一个返回用于解析占位符的解析器
    return new PathMatchingResourcePatternResolver(this);
  }

方法3: setParent

    public void setParent(@Nullable ApplicationContext parent) {
    //赋值传进来的父上下文到当前上下文属性中
    this.parent = parent;
    if (parent != null) {
      //获取父容器系统环境属性
      Environment parentEnvironment = parent.getEnvironment();
      if (parentEnvironment instanceof ConfigurableEnvironment) {
        //合并到当前上下文中
        getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
      }
    }
  }

方法4:setConfigLocations

  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;
    }
  }

this.configLocations[i] = resolvePath(locations[i]).trim(); 见方法5

方法5: resolvePath

  protected String resolvePath(String path) {
    //获取上下文中的系统环境属性,并调用其路径解析方法,如含有占位符:${path}
    return getEnvironment().resolveRequiredPlaceholders(path);
  }

getEnvironment() 见方法6

resolveRequiredPlaceholders(path) 见方法9

方法6:getEnvironment

  public ConfigurableEnvironment getEnvironment() {
    if (this.environment == null) {
      //创建系统环境属性
      this.environment = createEnvironment();
    }
    return this.environment;
  }


createEnvironment(); 见方法7

方法7:createEnvironment

  protected ConfigurableEnvironment createEnvironment() {
    //创建一个标准的环境属性
    return new StandardEnvironment();
  }

new StandardEnvironment(); 见方法8

方法8:StandardEnvironment


cad2eb42b6255739b6eb95453562fd59_f8c5c8e905c9430db79ae65b264e5662.png

进入这个类之后,我们看到它并没有构造方法,而且也没有resolveRequiredPlaceholders()方法,不过我们可以看到它继承了AbstractEnvironment方法,所以实例化时,肯定是调用父类的构造器。

方法9 resolveRequiredPlaceholdersresolveRequiredPlaceholders方法其实调用的是父类AbstractEnvironment中的方法,其类图关系如下:


  @Override
  public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    return this.propertyResolver.resolveRequiredPlaceholders(text);
  }

resolveRequiredPlaceholders(text) 见方法10

方法10 resolveRequiredPlaceholders

  @Override
  public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    if (this.strictHelper == null) {
      //创建占位符解析器
      this.strictHelper = createPlaceholderHelper(false);
    }
    //解析并返回处理后的配置文件路径
    return doResolvePlaceholders(text, this.strictHelper);
  }

doResolvePlaceholders(text, this.strictHelper) 见方法11

方法11 doResolvePlaceholders

  public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
    Assert.notNull(value, "'value' must not be null");
    return parseStringValue(value, placeholderResolver, null);
  }
  protected String parseStringValue(
      String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
    //这里的this.placeholderPrefix 默认值为${
    int startIndex = value.indexOf(this.placeholderPrefix);
    //判断下传进来的路径是否有${开头的占位符
    if (startIndex == -1) {
      return value;
    }
    StringBuilder result = new StringBuilder(value);
    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 == null) {
          visitedPlaceholders = new HashSet<>(4);
        }
        if (!visitedPlaceholders.add(originalPlaceholder)) {
          throw new IllegalArgumentException(
              "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
        }
        // 递归调用,看占位符内容是否还有包含占位符 ,比如${${user}}
        placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
        // 通过占位符内容名称去查询出,获取具体值,如user从environment中查出当前电脑名称
        String propVal = placeholderResolver.resolvePlaceholder(placeholder);
        if (propVal == null && this.valueSeparator != null) {
          //如果查不到可能是以键值对的形式 如 name:zhangsan
          //这时候我们要解析出KEY跟VALUE
          //获取:符号的下标
          int separatorIndex = placeholder.indexOf(this.valueSeparator);
          if (separatorIndex != -1) {
            //获取出key的值,如name
            String actualPlaceholder = placeholder.substring(0, separatorIndex);
            //获取出value的值,如zhangsan
            String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
            //通过key值名称去查询,如果查不到则以actualPlaceholder为默认值进行赋值
            propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
            if (propVal == null) {
              propVal = defaultValue;
            }
          }
        }
        if (propVal != null) {
          //递归调用,解析包含在以前解析的占位符值中的占位符。
          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 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 value \"" + value + "\"");
        }
        visitedPlaceholders.remove(originalPlaceholder);
      }
      else {
        startIndex = -1;
      }
    }
    return result.toString();
  }

至此占位符解析结束,返回解析后的值赋值给上下文的configLocations数组;

总结

refresh方法调用前的解析工作大概就是这样,这里只是Spring IOC的流程,如果是Springboot的话,会有一些方法重载,代码会更复杂。刷新前的准备工作,主要就是创建StandEnvironment属性类,解析占位符等操作;后面的文章再来写下refresh中的核心方法,文章会慢慢更新的,文章可能会有些理解不到位的地方,大家有更好的见解可以交流学习!


目录
相关文章
|
3天前
|
XML Java 数据格式
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
这篇文章是Spring5框架的实战教程,主要介绍了如何在Spring的IOC容器中通过XML配置方式使用外部属性文件来管理Bean,特别是数据库连接池的配置。文章详细讲解了创建属性文件、引入属性文件到Spring配置、以及如何使用属性占位符来引用属性文件中的值。
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
|
3天前
|
XML Java 数据格式
Spring5入门到实战------4、IOC容器-Bean管理XML方式、集合的注入(二)
这篇文章是Spring5框架的实战教程,主题是IOC容器中Bean的集合属性注入,通过XML配置方式。文章详细讲解了如何在Spring中注入数组、List、Map和Set类型的集合属性,并提供了相应的XML配置示例和Java类定义。此外,还介绍了如何在集合中注入对象类型值,以及如何使用Spring的util命名空间来实现集合的复用。最后,通过测试代码和结果展示了注入效果。
Spring5入门到实战------4、IOC容器-Bean管理XML方式、集合的注入(二)
|
3天前
|
XML Java 数据格式
Spring5入门到实战------6、IOC容器-Bean管理XML方式(自动装配)
这篇文章是Spring5框架的入门教程,详细讲解了IOC容器中Bean的自动装配机制,包括手动装配、`byName`和`byType`两种自动装配方式,并通过XML配置文件和Java代码示例展示了如何在Spring中实现自动装配。
Spring5入门到实战------6、IOC容器-Bean管理XML方式(自动装配)
|
3天前
|
XML Java 数据格式
Spring5入门到实战------8、IOC容器-Bean管理注解方式
这篇文章详细介绍了Spring5框架中使用注解进行Bean管理的方法,包括创建Bean的注解、自动装配和属性注入的注解,以及如何用配置类替代XML配置文件实现完全注解开发。
Spring5入门到实战------8、IOC容器-Bean管理注解方式
|
3天前
|
XML Java 数据格式
Spring5入门到实战------2、IOC容器底层原理
这篇文章深入探讨了Spring5框架中的IOC容器,包括IOC的概念、底层原理、以及BeanFactory接口和ApplicationContext接口的介绍。文章通过图解和实例代码,解释了IOC如何通过工厂模式和反射机制实现对象的创建和管理,以及如何降低代码耦合度,提高开发效率。
Spring5入门到实战------2、IOC容器底层原理
|
设计模式 Java Nacos
Spring ApplicationContext的事件机制是什么?在Nacos中如何应用?
Spring ApplicationContext的事件机制是什么?在Nacos中如何应用?
180 0
Spring ApplicationContext的事件机制是什么?在Nacos中如何应用?
|
21天前
|
Java 测试技术 数据库
Spring Boot中的项目属性配置
本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。
|
1月前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
87 0