【Spring】SpringBoot的扩展点之ApplicationContextInitializer

news2024/11/15 21:49:02

简介

其实spring启动步骤中最早可以进行扩展的是实现ApplicationContextInitializer接口。来看看这个接口的注释。

package org.springframework.context;

/**
 * Callback interface for initializing a Spring {@link ConfigurableApplicationContext}
 * prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}.
 *
 * <p>Typically used within web applications that require some programmatic initialization
 * of the application context. For example, registering property sources or activating
 * profiles against the {@linkplain ConfigurableApplicationContext#getEnvironment()
 * context's environment}. See {@code ContextLoader} and {@code FrameworkServlet} support
 * for declaring a "contextInitializerClasses" context-param and init-param, respectively.
 *
 * <p>{@code ApplicationContextInitializer} processors are encouraged to detect
 * whether Spring's {@link org.springframework.core.Ordered Ordered} interface has been
 * implemented or if the {@link org.springframework.core.annotation.Order @Order}
 * annotation is present and to sort instances accordingly if so prior to invocation.
 *
 * @author Chris Beams
 * @since 3.1
 * @param <C> the application context type
 * @see org.springframework.web.context.ContextLoader#customizeContext
 * @see org.springframework.web.context.ContextLoader#CONTEXT_INITIALIZER_CLASSES_PARAM
 * @see org.springframework.web.servlet.FrameworkServlet#setContextInitializerClasses
 * @see org.springframework.web.servlet.FrameworkServlet#applyInitializers
 */
@FunctionalInterface
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

	/**
	 * Initialize the given application context.
	 * @param applicationContext the application to configure
	 */
	void initialize(C applicationContext);

}

简要的说明一下,有这么几点:

  1. 实现这个接口之后,它的initialize方法会在容器ConfigurableApplicationContext刷新之前触发。
  2. 它通常用于在容器初始化之前进行一些程序上的操作,比如说注册一些环境变量或者读取一些配置文件。
  3. 它可以使用@Order指定优先级

实现方式

它有三种实现方式:

  1. 通过SPI机制实现,在resources/META-INF/spring.factories中定义如下内容:
    org.springframework.context.ApplicationContextInitializer=com.alone.spring.aop.demo.config.ContextInitializerTest
/**
 * spring扩展点 ApplicationContextInitializer
 */
@Slf4j
public class ContextInitializerTest implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        log.info("ContextInitializerTest 开始加载");
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String, Object> initMap = new HashMap<>();
        initMap.put("20231116", "This is init");
        MapPropertySource propertySource = new MapPropertySource("ContextInitializerTest", initMap);
        environment.getPropertySources().addLast(propertySource);
        log.info("ContextInitializerTest 加载结束");
    }
}
  1. application.yml中定义如下内容:
context:
  initializer:
    classes: com.alone.spring.aop.demo.config.YmlApplicationContextInitializer
@Slf4j
public class YmlApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        log.info("这是yml的ApplicationContextInitializer");
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String, Object> initMap = new HashMap<>();
        initMap.put("20231116", "YmlApplicationContextInitializer");
        MapPropertySource propertySource = new MapPropertySource("ContextInitializerTest", initMap);
        environment.getPropertySources().addLast(propertySource);
        log.info("YmlApplicationContextInitializer 加载结束");
    }
}
  1. 在启动类中进行注册:
public static void main(String[] args) {
    SpringApplication springApplication = new SpringApplication(SpringbootApplication.class);
    springApplication.addInitializers(new MainFlagApplicationContextInitializer());
    springApplication.run();
}
@Component
@Slf4j
public class MainFlagApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        log.info("这是main的ApplicationContextInitializer");
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String, Object> initMap = new HashMap<>();
        initMap.put("20231116", "MainFlagApplicationContextInitializer");
        MapPropertySource propertySource = new MapPropertySource("ContextInitializerTest", initMap);
        environment.getPropertySources().addLast(propertySource);
        log.info("MainFlagApplicationContextInitializer 加载结束");
    }
}

三者的加载顺序是:
application.yml >spring.factories >启动类
在这里插入图片描述

源码分析

从启动类的new SpringApplication(SpringbootApplication.class)开始分析:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = new ArrayList<>(
            getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

看到上面第8行(源码266行)中出现了ApplicationContextInitializer.class猜想它肯定是在读取相关的配置,跟进去发现出现了下面这行。

在这里插入图片描述

这里是读取了spring.factories中的内容,但看它的结果发现不止我们自定义的类一个,说明springboot内置了一些ApplicationContextInitializer,后续我们再看它们具体的作用,这里先截图列出按下不表。

在这里插入图片描述

然后沿如下的调用栈可以找到initializer.initialize(context);这一行调用ApplicationContextInitializer的语句。
springApplication.run()
run:306, SpringApplication (org.springframework.boot)
prepareContext:383, SpringApplication (org.springframework.boot)
applyInitializers:614, SpringApplication (org.springframework.boot)

框起来的方法会对所有的initializer进行排序,排序后的结果见左边。
在执行到DelegatingApplicationContextInitializer时会去读取环境中的context.initializer.classes,也就是application.yml中配置的内容执行。所以会先执行yml配置的initializer.
在这里插入图片描述

以上总结一下是这样的:
在这里插入图片描述

大致调用的流程图是:
在这里插入图片描述

系统内置初始化类

最后我们来看看上面提到的系统内置的初始化类都有些什么作用。

SharedMetadataReaderFactoryContextInitializer

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    BeanFactoryPostProcessor postProcessor = new CachingMetadataReaderFactoryPostProcessor(applicationContext);
    applicationContext.addBeanFactoryPostProcessor(postProcessor);
}

初始化了一个CachingMetadataReaderFactoryPostProcessor至容器中

DelegatingApplicationContextInitializer

@Override
public void initialize(ConfigurableApplicationContext context) {
    ConfigurableEnvironment environment = context.getEnvironment();
    List<Class<?>> initializerClasses = getInitializerClasses(environment);
    if (!initializerClasses.isEmpty()) {
        applyInitializerClasses(context, initializerClasses);
    }
}

执行context.initializer.classes配置的initializer。

ContextIdApplicationContextInitializer

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    ContextId contextId = getContextId(applicationContext);
    applicationContext.setId(contextId.getId());
    applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(), contextId);
}

private ContextId getContextId(ConfigurableApplicationContext applicationContext) {
    ApplicationContext parent = applicationContext.getParent();
    if (parent != null && parent.containsBean(ContextId.class.getName())) {
        return parent.getBean(ContextId.class).createChildId();
    }
    return new ContextId(getApplicationId(applicationContext.getEnvironment()));
}

private String getApplicationId(ConfigurableEnvironment environment) {
    String name = environment.getProperty("spring.application.name");
    return StringUtils.hasText(name) ? name : "application";
}

设置容器的id,值取自spring.application.name配置,默认是application

ConditionEvaluationReportLoggingListener

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
    applicationContext.addApplicationListener(new ConditionEvaluationReportListener());
    if (applicationContext instanceof GenericApplicationContext) {
        // Get the report early in case the context fails to load
        this.report = ConditionEvaluationReport.get(this.applicationContext.getBeanFactory());
    }
}

注册了一个ConditionEvaluationReportListener

RestartScopeInitializer

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    applicationContext.getBeanFactory().registerScope("restart", new RestartScope());
}

自动重启相关。

ConfigurationWarningsApplicationContextInitializer

@Override
public void initialize(ConfigurableApplicationContext context) {
    context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
}

初始化一个ConfigurationWarningsPostProcessor用于记录公共的容器配置错误信息。

RSocketPortInfoApplicationContextInitializer

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    applicationContext.addApplicationListener(new Listener(applicationContext));
}

增加了一个监听器用于监听RSockerServer的端口是否正常。
在这里插入图片描述

ServerPortInfoApplicationContextInitializer

/**
 * {@link ApplicationContextInitializer} that sets {@link Environment} properties for the
 * ports that {@link WebServer} servers are actually listening on. The property
 * {@literal "local.server.port"} can be injected directly into tests using
 * {@link Value @Value} or obtained via the {@link Environment}.
 * <p>
 * If the {@link WebServerInitializedEvent} has a
 * {@link WebServerApplicationContext#getServerNamespace() server namespace} , it will be
 * used to construct the property name. For example, the "management" actuator context
 * will have the property name {@literal "local.management.port"}.
 * <p>
 * Properties are automatically propagated up to any parent context.
 *
 * @author Dave Syer
 * @author Phillip Webb
 * @since 2.0.0
 */

@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
    applicationContext.addApplicationListener(this);
}

向容器中增加一个监听器用于检测WebServer的端口是否正常监听。

参考资料

  1. SpringBoot系统初始化器使用及源码解析(ApplicationContextInitializer)
  2. 跟我一起阅读SpringBoot源码(九)——初始化执行器
  3. Springboot扩展点之ApplicationContextInitializer

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1232002.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ubuntu——卸载、安装CUDA

【注】WSL的Ubuntu是不用安装CUDA的&#xff0c;因为它使用的是Windows的显卡驱动&#xff0c;所以如果WSL的CUDA出了问题&#xff0c;重新安装WSL即可&#xff01; 1、卸载 安装完CUDA后&#xff0c;会提示如果要卸载CUDA可以使用下列方法。 首先终端进入cuda-uninstaller所…

crmchat安装搭建教程文档 bug问题调试

一、安装PHP插件&#xff1a;fileinfo、redis、swoole4。 二、删除PHP对应版本中的 proc_open禁用函数。 一、设置网站运行目录public&#xff0c; 二、设置PHP版本选择纯静态。 三、可选项如有需求则开启SSL,配置SSL证书&#xff0c;开启强制https域名。 四、添加反向代理。 …

时间序列与 Statsmodels:预测所需的基本概念(1)

后文&#xff1a;时间序列与 statsmodels&#xff1a;预测所需的基本概念&#xff08;2&#xff09;-CSDN博客 一、说明 本博客解释了理解时间序列的基本概念&#xff1a;趋势、季节性、白噪声、平稳性&#xff0c;并使用自回归、差分和移动平均参数进行预测示例。这是理解任何…

计算机网络(持续更新…)

文章目录 一、概述1. 计网概述⭐ 发展史⭐ 基本概念⭐ 分类⭐ 数据交换方式&#x1f970; 小练 2. 分层体系结构⭐ OSI 参考模型⭐TCP/IP 参考模型&#x1f970; 小练 二、物理层1. 物理层概述⭐ 四个特性 2. 通信基础⭐ 重点概念⭐ 极限数据传输率⭐ 信道复用技术&#x1f389…

nodejs微信小程序 +python+PHP+图书销售管理系统的设计与实现-网上书店-图书商城-计算机毕业设计

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

Redis-高性能原理剖析

redis安装 下载地址&#xff1a;http://redis.io/download 安装步骤&#xff1a; # 安装gcc yum install gcc# 把下载好的redis-5.0.3.tar.gz放在/usr/local文件夹下&#xff0c;并解压 wget http://download.redis.io/releases/redis-5.0.3.tar.gz tar -zxvf redis-5.0.3.tar…

Java八股文(急速版)

Redis八股文 我看你在做项目的时候都使用到redis&#xff0c;你在最近的项目中哪些场景下使用redis呢? 缓存和分布式锁都有使用到。 问&#xff1a;说说在缓存方面使用 1.在我最写的物流项目中就使用redis作为缓存&#xff0c;当然在业务中还是比较复杂的。 2.在物流信息…

归并排序知识总结

归并排序思维导图&#xff1a; 知识点&#xff1a;如果原序列中两个数的值是相同的&#xff0c;它们在排完序后&#xff0c;它们的位置不发生变化&#xff0c;那么这个排序是稳定的。快速排序是不稳定的&#xff0c;归并排序是稳定的。 快排变成稳定的>使快排排序数组中的每…

Java格式化类Format

文章目录 Format介绍Format方法- format&#xff08;格式化&#xff09;- parseObject&#xff08;解析&#xff09; 格式化分类日期时间格式化1. DateFormat常用方法getInstancegetDateInstancegetTimeInstancegetDateTimeInstance 方法入参styleLocale 2. SimpleDateFormat常…

Redis入门与应用

目录 Redis的技术全景 两大维度 三大主线 Redis的版本选择与安装 Redis的linux安装 Redis的启动 默认配置 带参数启动 配置文件启动 操作 停止 Redis全局命令 键名的生产实践 Redis常用数据结构 字符串&#xff08;String&#xff09; 操作命令 set 设置值 g…

科技云报道:全球勒索攻击创历史新高,如何建立网络安全的防线?

科技云报道原创。 最简单的方式&#xff0c;往往是最有效的&#xff0c;勒索软件攻击就属于这类。 近两年&#xff0c;随着人类社会加速向数字世界进化&#xff0c;勒索软件攻击成为网络安全最为严重的威胁之一。今年以来&#xff0c;勒索软件攻击在全球范围内呈现快速上升态…

Day36力扣打卡

打卡记录 T 秒后青蛙的位置&#xff08;DFS&#xff09; 链接 class Solution:def frogPosition(self, n: int, edges: List[List[int]], t: int, target: int) -> float:g [[] for _ in range(n 1)]for x, y in edges:g[x].append(y)g[y].append(x)g[1].append(0)ans …

11.16~11.19绘制图表,导入EXCEL中数据,进行拟合

这个错误通常是由于传递给curve_fit函数的数据类型不正确引起的。根据你提供的代码和错误信息&#xff0c;有几个可能的原因&#xff1a; 数据类型错误&#xff1a;请确保ce_data、lg_data和product_data是NumPy数组或类似的可迭代对象&#xff0c;且其元素的数据类型为浮点数。…

系列二、Lock接口

一、多线程编程模板 线程 操作 资源类 高内聚 低耦合 二、实现步骤 1、创建资源类 2、资源类里创建同步方法、同步代码块 三、12306卖票程序 3.1、synchronized实现 3.1.1、Ticket /*** Author : 一叶浮萍归大海* Date: 2023/11/20 8:54* …

城市智慧路灯智能照明管理系统简介

城市路灯存在着开关灯控制方式单、亮灯时间不准确、巡查困难、故障处理不及时、亮灯率无法把控等问题&#xff0c;从而导致路灯系统能耗高&#xff0c;维护成本高。传统的路灯控制系统已无法满足智慧城市管理的需要&#xff0c;智能路灯照明控制系统从而得到广泛应用。 叁仟智…

基于安卓android微信小程序的好物分享系统

运行环境 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&a…

React整理总结(五、Redux)

1.Redux核心概念 纯函数 确定的输入&#xff0c;一定会产生确定的输出&#xff1b;函数在执行过程中&#xff0c;不能产生副作用 store 存储数据 action 更改数据 reducer 连接store和action的纯函数 将传入的state和action结合&#xff0c;生成一个新的state dispatc…

Azure 机器学习 - 搜索中的检索增强 (RAG)

目录 一、Azure AI 信息检索系统介绍二、采用 Azure AI 搜索的 RAG 方法三、适合 Azure AI 搜索的自定义 RAG 模式四、Azure AI 搜索中的可搜索内容五、Azure AI 搜索中的内容检索构建查询响应按相关性排名适用于 RAG 方案的 Azure AI 搜索查询的示例代码 六、集成代码和 LLM七…

时间序列预测实战(十七)PyTorch实现LSTM-GRU模型长期预测并可视化结果(附代码+数据集+详细讲解)

一、本文介绍 本文给大家带来的实战内容是利用PyTorch实现LSTM-GRU模型&#xff0c;LSTM和GRU都分别是RNN中最常用Cell之一&#xff0c;也都是时间序列预测中最常见的结构单元之一&#xff0c;本文的内容将会从实战的角度带你分析LSTM和GRU的机制和效果&#xff0c;同时如果你…

Three.js相机模拟

有没有想过如何在 3D Web 应用程序中模拟物理相机? 在这篇博文中,我将向你展示如何使用 Three.js和 OpenCV 来完成此操作。 我们将从模拟针孔相机模型开始,然后添加真实的镜头畸变。 具体来说,我们将仔细研究 OpenCV 的两个失真模型,并使用后处理着色器复制它们。 拥有逼…