【Spring】SpringBoot的扩展点之ApplicationContextInitializer

简介

其实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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】【开发】使用sed命令遇到的乱码问题

&#x1f41a;作者简介&#xff1a;花神庙码农&#xff08;专注于Linux、WLAN、TCP/IP、Python等技术方向&#xff09;&#x1f433;博客主页&#xff1a;花神庙码农 &#xff0c;地址&#xff1a;https://blog.csdn.net/qxhgd&#x1f310;系列专栏&#xff1a;Linux技术&…

蓝桥杯每日一题2023.11.20

题目描述 “蓝桥杯”练习系统 (lanqiao.cn) 题目分析 方法一&#xff1a;暴力枚举&#xff0c;如果说数字不在正确的位置上也就意味着这个数必须要改变&#xff0c;进行改变记录即可 #include<bits/stdc.h> using namespace std; const int N 2e5 10; int n, a[N], …

基于野狗算法优化概率神经网络PNN的分类预测 - 附代码

基于野狗算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于野狗算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于野狗优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

【教3妹学编辑-mysql】详解数据库三大范式

什么是范式 简单地理解就是&#xff1a;数据库设计时遵循的规范 三大范式 数据库三大范式包含&#xff1a;1、第一范式(1NF)&#xff1b;2、第二范式(2NF)&#xff1b;3、第三范式(3NF)。其中&#xff0c;第一范式(1NF)的要求是属性不可分割&#xff0c;第二范式(2NF)的要求是…

Java基础-----正则表达式

文章目录 1.简介2.目的3.学习网站4.常用匹配字符5.String类中用到正则表达式的方法 1.简介 又叫做规则表达式。是一种文本模式&#xff0c;包括普通字符和特殊字符&#xff08;元字符&#xff09;。正则使用单个字符来描述、匹配一系列某个句法规则的字符串&#xff0c;通常用…

机器学习笔记 - Ocr识别中的CTC算法原理概述

一、文字识别 在文本检测步骤中,分割出了文本区域。现在需要识别这些片段中存在哪些文本。 机器学习笔记 - Ocr识别中的文本检测EAST网络概述-CSDN博客文章浏览阅读300次。在 EAST 网络的这个分支中,它合并了 VGG16 网络不同层的特征输出。现在,该层之后的特征大小将等于 p…

《崩坏:星穹铁道》1.5仙舟罗浮-绥园全宝箱攻略

大家好&#xff0c;我是闲游盒小盒子&#xff0c;本篇来说下崩铁1.5版本仙舟罗浮-绥园的全宝箱攻略&#xff0c;共有19个宝箱加1个扑满&#xff1b;做完间章可获取前14个普通宝箱加2个精英怪宝箱&#xff0c;以及1个扑满&#xff1b;完成《狐斋志异》全任务可获得另外3个宝箱。…

Wireshark抓包:理解TCP三次握手和四次挥手过程

TCP是一种面向连接、端到端可靠的协议&#xff0c;它被设计用于在互联网上传输数据和确保成功传递数据和消息。本节来介绍一下TCP中的三次握手和四次挥手。 文章目录 1 TCP头部格式2 wireshark抓包分析2.1 SEQ和ACK2.2 三次握手2.3 四次挥手 3 程序 1 TCP头部格式 TCP头部占据…

Unity--互动组件(Scrollbar)||Unity--互动组件(DropDown )

此组件中的&#xff0c;交互&#xff0c;过渡&#xff0c;导航与文章&#xff08;Unity--互动组件&#xff08;Button&#xff09;&#xff09;中的介绍如同&#xff1b; handle rect&#xff1a;&#xff08;父节点矩形&#xff09; 用于控件的滑动“句柄”部分的图形&#xf…

Apache Airflow (十二) :PythonOperator

&#x1f3e1; 个人主页&#xff1a;IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 &#x1f6a9; 私聊博主&#xff1a;加入大数据技术讨论群聊&#xff0c;获取更多大数据资料。 &#x1f514; 博主个人B栈地址&#xff1a;豹哥教你大数据的个人空间-豹…

VMware创建Linux虚拟机之(三)Hadoop安装与配置及搭建集群

Hello&#xff0c;world&#xff01; &#x1f412;本篇博客使用到的工具有&#xff1a;VMware16 &#xff0c;Xftp7 若不熟悉操作命令&#xff0c;推荐使用带GUI页面的CentOS7虚拟机 我将使用带GUI页面的虚拟机演示 虚拟机&#xff08;Virtual Machine&#xff09; 指通过…

使用Python的turtle模块绘制玫瑰花图案(含详细Python代码与注释)

1.1引言 turtle模块是Python的标准库之一&#xff0c;它提供了一个绘图板&#xff0c;让我们可以在屏幕上绘制各种图形。通过使用turtle&#xff0c;我们可以创建花朵、叶子、复杂的图案等等。本博客将介绍如何使用turtle模块实现绘制图形的过程&#xff0c;并展示最终结果。 …

matlab 坡度滤波算法地面分割

目录 一、算法原理1、实现流程2、参考文献二、代码实现三、结果展示四、测试数据一、算法原理 1、实现流程 1、格网示意图 2、计算格网行列数 公式中的特殊符号为向上取整,

从0开始学习JavaScript--JavaScript使用Promise

JavaScript中的异步编程一直是开发中的重要话题。传统的回调函数带来了回调地狱和代码可读性的问题。为了解决这些问题&#xff0c;ES6引入了Promise&#xff0c;一种更现代、更灵活的异步编程解决方案。本文将深入探讨JavaScript中如何使用Promise&#xff0c;通过丰富的示例代…

C++二分算法:找到最接近目标值的函数值

本文涉及的基础知识点 二分查找算法合集 题目 Winston 构造了一个如上所示的函数 func 。他有一个整数数组 arr 和一个整数 target &#xff0c;他想找到让 |func(arr, l, r) - target| 最小的 l 和 r 。 请你返回 |func(arr, l, r) - target| 的最小值。 请注意&#xff0c…

读像火箭科学家一样思考笔记04_第一性原理(下)

1. 来自无形规则的阻力 1.1. 无形规则 1.1.1. 僵化成规则的不必要习惯和行为 1.1.2. 不像有形的书面规则 1.1.2.1. 书面规则出现在标准操作流程中&#xff0c;可以修改或删除 1.1.3. 成文的规则可能会抗拒变革&#xff0c;但无形规则却更加顽固 1.1.4. 我们为强加在自己身…

华为---OSPF网络虚连接(Virtual Link)简介及示例配置

OSPF网络虚连接&#xff08;Virtual Link&#xff09;简介 为了避免区域间的环路&#xff0c;OSPF规定不允许直接在两个非骨干区域之间发布路由信息&#xff0c;只允许在一个区域内部或者在骨干区域和非骨干区域之间发布路由信息。因此&#xff0c;每个ABR都必须连接到骨干区域…

【计算机网络笔记】路由算法之链路状态路由算法

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

CTF-PWN-小tips

文章目录 overflowscanfgetreadstrcpystrcat Find string in gdbgdbgdb peda Binary ServiceFind specific function offset in libc手工自动 Find /bin/sh or sh in library手动自动 Leak stack addressFork problem in gdbSecret of a mysterious section - .tlsPredictable …

手机照片误删解决方法分享

几个要点 1.检查回收站&#xff1a;一些情况下&#xff0c;我们会在删除文件时将它们移动到回收站中&#xff0c;查看回收站中是否有被删除的照片&#xff0c;这样可以直接恢复文件。 2.使用手机自带的恢复功能&#xff1a;一些手机自带照片恢复功能&#xff0c;可尝试在相册…