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】 集合中


目录
相关文章
|
6月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
10月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
597 70
|
12月前
|
人工智能 自然语言处理 Java
Spring 集成 DeepSeek 的 3大方法(史上最全)
DeepSeek 的 API 接口和 OpenAI 是兼容的。我们可以自定义 http client,按照 OpenAI 的rest 接口格式,去访问 DeepSeek。自定义 Client 集成DeepSeek ,可以通过以下步骤实现。步骤 1:准备工作访问 DeepSeek 的开发者平台,注册并获取 API 密钥。DeepSeek 提供了与 OpenAI 兼容的 API 端点(例如),确保你已获取正确的 API 地址。
Spring 集成 DeepSeek 的 3大方法(史上最全)
|
10月前
|
缓存 安全 Java
深入解析HTTP请求方法:Spring Boot实战与最佳实践
这篇博客结合了HTTP规范、Spring Boot实现和实际工程经验,通过代码示例、对比表格和架构图等方式,系统性地讲解了不同HTTP方法的应用场景和最佳实践。
957 5
|
10月前
|
Java Spring 容器
两种Spring Boot 项目启动自动执行方法的实现方式
在Spring Boot项目启动后执行特定代码的实际应用场景中,可通过实现`ApplicationRunner`或`CommandLineRunner`接口完成初始化操作,如系统常量或配置加载。两者均支持通过`@Order`注解控制执行顺序,值越小优先级越高。区别在于参数接收方式:`CommandLineRunner`使用字符串数组,而`ApplicationRunner`采用`ApplicationArguments`对象。注意,`@Order`仅影响Bean执行顺序,不影响加载顺序。
797 2
|
11月前
|
前端开发 Java 数据库连接
Spring MVC 扩展和SSM框架整合
通过以上步骤,我们可以将Spring MVC扩展并整合到SSM框架中。这个过程包括配置Spring MVC和Spring的核心配置文件,创建控制器、服务层和MyBatis的Mapper接口及映射文件。在实际开发中,可以根据具体业务需求进行进一步的扩展和优化,以构建更加灵活和高效的企业级应用程序。
295 5
|
11月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
291 0
|
Java 关系型数据库 数据库连接
Spring源码解析--深入Spring事务原理
本文将带领大家领略Spring事务的风采,Spring事务是我们在日常开发中经常会遇到的,也是各种大小面试中的高频题,希望通过本文,能让大家对Spring事务有个深入的了解,无论开发还是面试,都不会让Spring事务成为拦路虎。
396 1
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
588 5