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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 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】 集合中


目录
相关文章
|
3天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
3天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
8 1
|
4天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
11 1
|
8天前
|
存储 安全 Java
|
19天前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
|
19天前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
19天前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
35 0
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
19天前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
100 2