Spring 核心方法 refresh 刷新流程简要概述及相关源码扩展实现(一)(上)

简介: Spring 核心方法 refresh 刷新流程简要概述及相关源码扩展实现(一)

前言

在陈旧系统中都是使用 SSM 架构进行搭建,Spring 源码是后续深入理解学习 SpringBoot、SpringCloud 组件所必须依赖的,只是不再采用 XML 方式来进行配置使用,在这里,主要是回顾下最初在使用 Spring 时的核心类「ClassPathXmlApplicationContext」实例过程以及配置文件加载的详细过程,因为后续的核心方法 refresh 内容讲解也得从此处开始讲起.

文章内容有点长,可以分目录节点进行阅读,比较想深入理解的方法可以点击文章目录入口.

Spring 启动流程解析

新建 ApplicationContext 上下文对象整体流程如下

  1. PathMatchingResourcePatternResolver:创建一个资源模式解析器(其实就是用来解析 xml 配置文件),内部创建了 Ant 方式表达式语言匹配器
// 创建 Ant【表达式语言】 方式的路径匹配器
private PathMatcher pathMatcher = new AntPathMatcher();
  1. setConfigLocations:设置应用程序上下文的配置路径
  2. customizePropertySources:在创建标准化环境对象(StandardEnvironment)时 createEnvironment() 会执行父类【AbstractEnvironment】构造方法加载这两项值【systemProperties:系统属性值,systemEnvironment:系统环境值】
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()));
}
  1. resolveRequiredPlaceholders:解析占位符信息
    创建 PropertyPlaceholderHelper 处理类
    doResolvePlaceholders 执行解析操作
    在其内部递归调用 PropertyPlaceholderHelper#parseStringValue 方法从系统属性和系统环境中解析占用符拿到具体值,递归的原因是因为可能出现这种情况【spring-${username${nickname}}.xml】

Refresh 内部方法全解析

prepareRefresh

容器刷新前的准备工作

protected void prepareRefresh() {
    // 设置容器启动的时间
    this.startupDate = System.currentTimeMillis();
    // 容器的关闭标志位
    this.closed.set(false);
    // 容器的激活标志位
    this.active.set(true);
    // 记录日志
    if (logger.isDebugEnabled()) {
      if (logger.isTraceEnabled()) {
        logger.trace("Refreshing " + this);
      }
      else {
        logger.debug("Refreshing " + getDisplayName());
      }
    }
    // Initialize any placeholder property sources in the context environment.
    // 留给子类覆盖扩展,初始化属性资源,钩子函数
    initPropertySources();
    // Validate that all properties marked as required are resolvable:
    // see ConfigurablePropertyResolver#setRequiredProperties
    // 创建并获取环境对象,验证需要的属性文件是否都已经放入环境中,如果没有放入就抛出异常
    getEnvironment().validateRequiredProperties();
    // Store pre-refresh ApplicationListeners...
    // 判断刷新前的应用程序监听器集合是否为空,如果为空,则将监听器添加到此集合中
    if (this.earlyApplicationListeners == null) {
      this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
    }
    else {
      // Reset local application listeners to pre-refresh state.
      // 如果不等于空,则清空集合元素对象
      this.applicationListeners.clear();
      this.applicationListeners.addAll(this.earlyApplicationListeners);
    }
    // Allow for the collection of early ApplicationEvents,
    // to be published once the multicaster is available...
    // 创建刷新前的监听事件集合
    this.earlyApplicationEvents = new LinkedHashSet<>();
  }

设置容器的启动时间、设置活跃状态为 true、设置关闭状态为 false、获取 Environment 对象,并加载当前的系统属性值到 Environment 对象中、验证必需的属性是否在环境中存在,如果没有则抛出异常、准备监听器和监听事件的集合对象,默认为空的集合.

该方法流程里面有一个扩展点「初始化属性源交由子类去扩展,可以在其下自定义一些属性,要求其拥有其必须依赖的属性才能正常的运行」

public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext {
    public MyClassPathXmlApplicationContext(String... configLocations){
        super(configLocations);
    }
    @Override
    protected void initPropertySources() {
        System.out.println("扩展initPropertySource");
        // 若系统属性中不存在 username,会抛出异常.
        getEnvironment().setRequiredProperties("username");
    }
}

obtainFreshBeanFactory

创建容器对象【DefaultListableBeanFactory】加载 xml 配置文件的属性值到当前工厂中,最重要的就是 BeanDefinition

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
  // 初始化 BeanFactory,并进行 XML 文件读取,并将得到的 BeanFactory 记录在当前实体的属性中
  refreshBeanFactory();
  // 返回当前实体 beanFactory 属性
  return getBeanFactory();
}

AbstractApplicationContext 类以及实现子类

AbstractApplicationContext 子类 AbstractRefreshableApplicationContext 继承重写以下方法,XML 启动容器时是该入口来实现

因为 AbstractRefreshableApplicationContext 是 ClassPathXmlApplicationContext 的父类

@Override
protected final void refreshBeanFactory() throws BeansException {
  // 如果存在beanFactory,则销毁beanFactory
  if (hasBeanFactory()) {
    destroyBeans();
    closeBeanFactory();
  }
  try {
    // 创建DefaultListableBeanFactory对象
    DefaultListableBeanFactory beanFactory = createBeanFactory();
    // 为了序列化指定id,可以从id反序列化到beanFactory对象
    beanFactory.setSerializationId(getId());
    // 定制beanFactory,设置相关属性,包括是否允许覆盖同名称的不同定义的对象以及循环依赖
    customizeBeanFactory(beanFactory);
    // 初始化documentReader,并进行XML文件读取及解析,默认命名空间的解析,自定义标签的解析
    loadBeanDefinitions(beanFactory);
    this.beanFactory = beanFactory;
  }
  catch (IOException ex) {
    throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
  }
}

AbstractApplicationContext 子类 GenericApplicationContext 继承重写以下方法,SpringBoot 是该入口来实现:

protected final void refreshBeanFactory() throws IllegalStateException {
  if (!this.refreshed.compareAndSet(false, true)) {
    throw new IllegalStateException(
        "GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
  }
  this.beanFactory.setSerializationId(getId());
}

涉及到 Web 容器启动流程中在 createApplicationContext 方法中会根据类型创建一个上下文对象:AnnotationConfigServletWebServerApplicationContext

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch(this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                break;
            case REACTIVE:
                contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                break;
            default:
                contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
            }
        } catch (ClassNotFoundException var3) {
            throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
        }
    }
    return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}

再查看它的类关系图就清楚了

AbstractRefreshableApplicationContext#obtainFreshBeanFactory 方法

接着继续分析 AbstractRefreshableApplicationContext#obtainFreshBeanFactory 方法的处理流程:

  1. 创建实例化 DefaultLisableBeanFactory 时,会构建其父类的构造方法,其逻辑会忽略要依赖的接口
public AbstractAutowireCapableBeanFactory() {
  super();
  // 忽略要依赖的接口
  ignoreDependencyInterface(BeanNameAware.class);
  ignoreDependencyInterface(BeanFactoryAware.class);
  ignoreDependencyInterface(BeanClassLoaderAware.class);
}
  1. 指定上下文工厂对象序列化 ID
  2. customizeBeanFactory:定制 beanFactory,设置相关属性,包括是否允许覆盖同名称的不同定义的对象以及循环依赖
// 此方法可以交由子类去自由扩展实现
@Override
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
  super.setAllowBeanDefinitionOverriding(false);
  super.setAllowCircularReferences(false);
  super.customizeBeanFactory(beanFactory);
}
  1. loadBeanDefinitions:初始化 documentReader,并进行 XML 文件读取及解析,默认命名空间的解析,自定义标签的解析【创建 BeanDefinition 核心步骤】

XmlWebApplicationContext#loadBeanDefinitions 方法

创建配置文件读取器:XmlBeanDefinitionReader

beanDefinitionReader#setEntityResolver(new ResourceEntityResolver(this)):设置实体解析器,有 DTD(document type Definition)、XSD(XML Schema Definition) 这两种解析方式,目前大部分使用的是后者

this.dtdResolver = new BeansDtdResolver();
// 当完成这行代码的调用之后,大家神奇的发现一件事情,schemaResolver 对象的 schemaMappings 属性被完成了赋值操作,但是你遍历完成所有代码后依然没有看到显式调用
// 其实此时的原理是非常简单的,我们在进行 debug 的时候,因为在程序运行期间需要显示当前类的所有信息,所以 idea 会帮助我们调用 toString 方法,只不过此过程我们识别不到而已,idea 中可以设置关闭 Debug 模式下 toString 方法调用
// toString(){return "EntityResolver using schema mappings " + getSchemaMappings();}
// 会去加载 META-INF/spring.schemas 文件下所有键值对
this.schemaResolver = new PluggableSchemaResolver(classLoader);

initBeanDefinitionReader(beanDefinitionReader):初始化 beanDefinitionReader 对象,此处设置配置文件是否要进行验证

loadBeanDefinitions(beanDefinitionReader):开始完成对 BeanDefinition 的加载,详细流程图如下:

解析 spring 配置文件整体流程: 这个解析过程是由 documentLoader 完成的,从 String[]—>string—>Resource[]—>resource,最终开始将 resource 转换为 InputStream 流对象后读取成一个 document 文档,新建 BeanDefinitionDocumentReader 对象后对 XML 中 BeanDefinition 进行解析,根据文档的节点信息封装成一个个的 BeanDefinition 对象,将其包装为 BeanDefinitionHolder 对象,并把 BeanDefinition、BeanName、alias 相关信息存入【beanDefinitionMap、beanDefinitionNames】集合中.

BeanDefinitionHolder 介绍:BeanDefinition 对象持有者,封装了 BeanDefinition,bean 名字和别名,用它来完成向 IOC 容器注册,得到这个 BeanDefinitionHolder 意味着 beanDefinition 是通过 BeanDefinitionParserDelegate 对 XML 元素的信息按照 spring bean 规则进行解析得到的

通过子节点来划分整个加载 XML 配置文件的流程:

  1. createReaderContext(resource):创建 XML 上下文读取解析器,同时会加载命名空间下的 handlers,存入在【META-INF/spring.handlers】文件中,以 key(命名空间路径)—>value(具体的 handler 类)方式存储.
  2. delegate.isDefaultNamespace(root):判定命名空间是否为默认的命名空间【http://www.springframework.org/schema/beans】如果是进行默认 parseDefaultElement(ele, delegate) 处理,无须去加载文件中的命名空间 handler 类;非默认命名空间,则需要去获取对应的 handler 进行处理
  3. 默认节点标签:import、alias、bean、beans

import 标签:会获取 resource 属性值,判定其是绝对还是相对的 URI【绝对 URI 直接将 resource 对象直接重新解析的一个流程,相对 URI 则匹配上前缀路径后再走这样的一个流程】

alias:给指定的 BeanName 注册 1~N 个别名,会对所有的 alias->name 关系进行校验,如果不满足就会抛出异常结束「beanName 与 alias 相同不记录 alias、alias 已存在集合中,判定是否允许覆盖:不允许抛出异常、检查别名关系中是否出现嵌套异常,如 A->B 可以但 B->A 就不可以了、将 alias->name 键值对存入集合中」

bean 标签:获取 id、name 属性,id 作为 BeanName,name 作为 alias,如果没有指定 BeanName 名称时,那么 Spring 会根据命名规则为当前 Bean 生成 BeanName 值

checkNameUniqueness(beanName, aliases, ele):会去校验 BeanName 的唯一性,如果存在了会抛出异常

parseBeanDefinitionElement(ele, beanName, containingBean):对 bean 元素进行详细解析

1、根据 class 属性获取全路径类名,反射获取其类实例

2、解析 bean 标签内的属性:depends-on、init-method、destroy-method

3、解析 bean 标签下子标签及其属性:lookup-method【执行新的逻辑需要新加 bean】、replaced-method【将之前方法执行的逻辑替换为新的】、property、构造函数参数

4、property 标签有以下约束:property 标签上不能同时有 value 和 ref 属性、 在有 value 或 ref 属性的同时不能再有子节点、property 标签上 value 和 ref 两个属性都没有也会报错

以上流程处理完调用 BeanDefinitionReaderUtils.registerBeanDefinition(definition, registry) 注册 Bean 信息放入到 【beanDefinitionMap、beanDefinitionNames】 集合中


目录
相关文章
|
7天前
|
Java 应用服务中间件 Nacos
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
21 0
|
10天前
|
监控 数据可视化 安全
一套成熟的Spring Cloud智慧工地平台源码,自主版权,开箱即用
这是一套基于Spring Cloud的智慧工地管理平台源码,具备自主版权,易于使用。平台运用现代技术如物联网、大数据等改进工地管理,服务包括建设各方,提供人员、车辆、视频监控等七大维度的管理。特色在于可视化管理、智能报警、移动办公和分布计算存储。功能涵盖劳务实名制管理、智能考勤、视频监控AI识别、危大工程监控、环境监测、材料管理和进度管理等,实现工地安全、高效的智慧化管理。
|
1天前
|
设计模式 安全 Java
【初学者慎入】Spring源码中的16种设计模式实现
以上是威哥给大家整理了16种常见的设计模式在 Spring 源码中的运用,学习 Spring 源码成为了 Java 程序员的标配,你还知道Spring 中哪些源码中运用了设计模式,欢迎留言与威哥交流。
21 0
|
4天前
|
XML Java 数据格式
手写spring第七章-完成便捷实现bean对象初始化和销毁方法
手写spring第七章-完成便捷实现bean对象初始化和销毁方法
6 0
|
4天前
|
设计模式 存储 Java
手写spring第二章-运用设计模式编写可扩展的容器
手写spring第二章-运用设计模式编写可扩展的容器
7 0
|
5天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
11天前
|
安全 Java API
第1章 Spring Security 概述(2024 最新版)(下)
第1章 Spring Security 概述(2024 最新版)
21 0
|
11天前
|
安全 Java 数据安全/隐私保护
第1章 Spring Security 概述(2024 最新版)(上)
第1章 Spring Security 概述(2024 最新版)
27 0
|
11天前
|
Java Maven Nacos
Spring Cloud Eureka 服务注册和服务发现超详细(附加--源码实现案例--及实现逻辑图)
Spring Cloud Eureka 服务注册和服务发现超详细(附加--源码实现案例--及实现逻辑图)
23 0
|
12天前
|
Java 关系型数据库 MySQL
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
UWB (ULTRA WIDE BAND, UWB) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。一套UWB精确定位系统,最高定位精度可达10cm,具有高精度,高动态,高容量,低功耗的应用。
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例