Springboot扩展点之ApplicationContextInitializer

news2024/11/15 15:53:19

前言        

        一提到Spring、Springoboot,很多人马上就会想到依赖注入、控制反转、自动装配、约定大于配置、使开发变得简单等等。但是如果仅仅会使用Springboot、SpringMVC完成一些增删改查,解决一些bug,那么实际上你并没有真的懂Spring、Springboot。Spring的核心是容器,Springboot更是封装了Spring,把复杂隐藏在内部,让其在使用上更简单,同时又预留了很多的扩展。所以我认为学会Springboot的简单使用只是一个开始,对业务开发更有参考和学习意义的是Springboot如何把复杂变得简单、预留的扩展接口又是如何使用的。

1. ApplicationContextInitializer的功能作用

  1. 在Spring容器初始化开始的时候,ApplicationContextInitializer接口的所有实现在类会被实例化;
  2. 在Spring容器刷新前,所有实现类的org.springframework.context.ApplicationContextInitializer#initialize方法会被调用,initialize方法的形参类型是ConfigurableApplicationContext,因此可以认为ApplicationContextInitializer实际上是Spring容器初始化前ConfigurableApplicationContext的回调接口,可以对上下文环境作一些操作,如运行环境属性注册、激活配置文件等
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

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

}

2. 三种实现方式

        下面通过一个具体实现类实现ApplicationContextInitializer接口,来分享一下其具体的实现方式。Springboot的扩展点ApplicationContextInitializer接口的实现主要分为两步:

1、实现ApplicationContextInitializer接口; 

        MyApplicationContextInitializer实现ApplicationContextInitializer接口,并打印一下系统相关属性的key、value

public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        Map<String, Object> systemProperties = applicationContext.getEnvironment().getSystemProperties();
        System.out.println("---------------start------------------");
        systemProperties.forEach((key,value)->{
            System.out.println("key:"+key+",value:"+value);
        }); System.out.println("---------------end------------------");
    }
}

2、把实现类注册到Spring容器中

把实现类MyApplicationContextInitializer注册到Spring容器中,主要有三种方式:

2.1 spring.factories

        在resources目录新建/META-INFI/spring.factories文件,并预置以下内容,即可完成自定义MyApplicationContextInitializer的注册;

org.springframework.context.ApplicationContextInitializer=com.fanfu.config.MyApplicationContextInitializer

2.2 application.properties

在application.properties文件中预置以下配置内容,即可完成自定义MyApplicationContextInitializer的注册;

context.initializer.classes=com.fanfu.config.MyApplicationContextInitializer

2.3 springApplication.addInitializers()

在springboot的启动类中,使用springApplication.addInitializers(new MyApplicationContextInitializer()),完成自定义MyApplicationContextInitializer的注册;

@SpringBootApplication
public class FanfuApplication {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(FanfuApplication.class);
        springApplication.addInitializers(new MyApplicationContextInitializer());
        springApplication.run(args);
    }
}

执行结果如下:(user.dir=项目的根目录)

3. 初始化时机

ApplicationContextInitializer接口的实现类的初始化,是在SpringApplication类的构造函数中,即spring容器初始化开始前,先通过 getSpringFactoriesInstances(...)得到所有实现类的集合,然后通过 setInitializers(...)注入到SpringApplication类的initializersn属性中;

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();
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}

4. 执行时机

ApplicationContextInitializer接口的实现类的执行时机是在org.springframework.boot.SpringApplication#prepareContext-->org.springframework.boot.SpringApplication#applyInitializers中,即spring容器正式刷新前,准备上下文环境时;

  1. getInitializers()得到,SpringApplication类的构造函数中通过 setInitializers(...)注入到SpringApplication类的initializersn属性中的所有ApplicationContextInitializer接口的实现类;
  2. 遍历这些实现类,并调用initialize()方法;
protected void applyInitializers(ConfigurableApplicationContext context) {
   for (ApplicationContextInitializer initializer : getInitializers()) {
      Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
            ApplicationContextInitializer.class);
      Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
      initializer.initialize(context);
   }
}

5. 内置的实现类

        Springboot内部也有一些内置的实现类,用于辅助Spring相关功能的实现,常见的实现类如下:

5.1 DelegatingApplicationContextInitializer

ApplicationContextInitializer的第二种实现方式(application.properties),在application.properties文件中配置context.initializer.classes=com.fanfu.config.MyApplicationContextInitializer,DelegatingApplicationContextInitializer的作用就是找到application.properties文件中配置的实现类实例化,并执行initialize()方法

5.2 ContextIdApplicationContextInitializer

ContextIdApplicationContextInitializer用于设置 Spring 应用上下文 ID,如果在application.properties中未设置spring.application.name,则默认为“application”

5.3 ConfigurationWarningsApplicationContextInitializer

ConfigurationWarningsApplicationContextInitializer用于报告 Spring 容器的一些常见的错误配置,可以看出,该初始化器为 context 增加了一个 Bean 的后置处理器。这个处理器是在注册 BeanDefinition 实例之后生效的,用于处理注册实例过程中产生的告警信息,其实就是通过日志打印出告警信息。

5.4 ServerPortInfoApplicationContextInitializer

ServerPortInfoApplicationContextInitializer除了实现了ApplicationContextInitializer接口外,还实现了ApplicationListener接口,ServerPortInfoApplicationContextInitializer作用就是把自己作为一个监听器注册到Spring的上下文环境中;

5.5 SharedMetadataReaderFactoryContextInitializer

实例化了一个org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer.CachingMetadataReaderFactoryPostProcessor对象并注册到了Spring的上下文环境中,CachingMetadataReaderFactoryPostProcessor是SharedMetadataReaderFactoryContextInitializer的一个内部类,这里就不具体展开了,有兴趣的小伙伴可以继续跟一下org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer.CachingMetadataReaderFactoryPostProcessor#postProcessBeanDefinitionRegistry方法了解一下具体的内容;

5.6 ConditionEvaluationReportLoggingListener

ConditionEvaluationReportLoggingListener从名字看是一个监听器,实际上不是,其内部类org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener.ConditionEvaluationReportListener才是实际的监听器,而ConditionEvaluationReportLoggingListener的作用就是把内部类ConditionEvaluationReportListener注册到Spring上下文环境中;

6. 总结

通过这篇文章,可以了解到:

第一,在Spring容器被刷新前,可以通过实现ApplicationContextInitializer接口对Spring上下文环境作一些配置或操作;

第二,ApplicationContextInitializer接口的实现方式有三种,可以根据项目需要选择合适的;

第三,了解了ApplicationContextInitializer接口实现类的初始化时机和执行时机,以及Springboot内置的具体实现类,可以学习到spring本身是如何利用扩展接口实现一些功能的,对实际的项目开发具有一定的参考意义。

示例代码地址:凡夫贩夫 / fanfu-web · GitCode(ApplicationContextInitializer分支)

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

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

相关文章

每天5分钟机器学习:线性判别分析LDA算法

本文重点 我们前面学习了PCA算法,本文我们将学习一种新的算法,这种算法和PCA非常相似,这个算法叫做线性判别分析,简称为LDA,也称为Fisher线性判别(Fisher Linear Discriminant,FLD),是模式识别的经典算法,在1996年由Belhumeur引入模式识别和人工智能领域。 LDA原理 …

毒鸡汤 | 云服务器部署项目

文章目录前言环境介绍安装PHP安装Apache安装MySQL上传项目访问网站前言 新年的第一篇文章&#xff0c;今年继续努力加油&#xff0c;继续产出高质量的文章。 前面用PHPstudy搭建了【毒鸡汤】项目&#xff0c;但是这样只能自己访问&#xff0c;别人是访问不了的&#xff0c;因此…

Redis 持久化机制

个人博客地址&#xff1a; 文章目录Redis 持久化机制1. 概述2. RDB2.1 手动快照2.2 自动快照3. AOF3.1 AOF机制3.2 AOF文件的重写4. 混合型持久化5. 总结Redis 持久化机制 1. 概述 Redis​为了保证性能&#xff0c;会将所有数据放在内存中&#xff0c;那么万一Redis宕机&…

AcWing 756. 蛇形矩阵

目录问题描述思路与代码1. 个人解法2. 官方题解问题描述 原题链接&#x1f517;&#xff1a;756. 蛇形矩阵 输入两个整数 nnn 和 mmm&#xff0c;输出一个 nnn 行 mmm 列的矩阵&#xff0c;将数字 111 到 nmnmnm 按照回字蛇形填充至矩阵中。 具体矩阵形式可参考样例。 输入格…

synchronized底层原理

synchronized是JVM内置锁&#xff0c;基于monitor机制实现&#xff0c;依赖底层操作系统的互斥源语Mutex&#xff08;互斥量&#xff09;&#xff0c;它是一个重量级锁&#xff0c;性能较低。 当然&#xff0c;JVM内置锁在1.5之后版本做了重大优化&#xff0c;如锁粗化&#xf…

[红明谷CTF 2021]JavaWeb

0x01 好久没打过ctf了&#xff0c;最近也在学Java 就看下java的题吧 WP 进入环境就提示访问 /login &#xff0c;访问之后 提示的 /json ,试着访问一下 给了 jessid&#xff0c;继续访问 /json 的话 又会跳转至 /login 应该是要传点username password 果然是了&#…

web制作网页

HTML(人的骨架): 标签 1.超文本标记性语言 2.当前最新版HTML5 3.URL统一资源定位器&#xff08;网址&#xff09; 4.网站首页名称index.html&#xff0c;default.htm&#xff0c;main.heml 基本结构&#xff1a; 注意&#xff1a;1.标签都是小写 2.标签成对写 3.代码要层次缩进…

JavaScript内存管理

JavaScript 是一个弱类型的、动态语言&#xff0c;在执行一段 JS 代码时&#xff0c;需要经历编译、执行、内存回收阶段。 一、编译阶段 JS 代码执行时&#xff0c;会首先创建全局执行上下文、以及函数执行上下文&#xff0c;上下文的执行顺序按照栈的方式进行调用&#xff0c…

面试题: JVM内存结构

目录目的资源概览JVM内存溢出分类类型1: OutOfMemoryError类型2: StackOverflowError方法区与永久代、元空间之间的关系目的 学习记录, 面试准备 资源 B站的一个讲高频面试题的一个学习视频 概览 线程私有 ① 程序计数器 ② 虚拟机栈线程共享 ① 堆 ② 方法区 JVM内存溢…

RSA密钥协商过程

目录 TSL握手过程 RSA密钥协商握手过程 TLS第一次握手 TLS第二次握手 客户端验证证书 TLS第三次握手 TLS 第四次握手 RSA 算法的缺陷 TSL握手过程 HTTP 由于是明文传输&#xff0c;所谓的明文&#xff0c;就是说客户端与服务端通信的信息都是肉眼可见的&#xff0c;随…

linux常用指令讲解

文章目录 前言一、指令详解总结前言 Linux是一种自由和开放源代码的类UNIX操作系统&#xff0c;该操作系统的内核由林纳斯托瓦兹在1991年首次发布&#xff0c;之后&#xff0c;在加上用户空间的应用程序之后&#xff0c;就成为了Linux操作系统。严格来讲&#xff0c;Linux只是操…

分布式锁的实现

目录分布式锁分布式锁的引出单体锁存在的问题分布式锁的引出分布式锁的设计思路分布式锁的常见应用场景分布式锁方案分布式锁 分布式锁的引出 单体锁存在的问题 在单体应用中&#xff0c;如果我们对共享数据不进行加锁操作&#xff0c;多线程操作共享数据时会出现数据一致性问…

【Kuangbin简单DP】平整数组

4562. 平整数组 - AcWing题库 题意&#xff1a; 思路&#xff1a; 一开始想的是 设DP状态是&#xff1a;dp[i][0/1]表示阶段到 i &#xff0c;然后前面是递增or递减的最小sum的集合 然后我是怎么转移的呢&#xff0c;现在看来感觉挺好笑的&#xff1a; #include <bits/…

Golang 函数使用的注意事项和细节

来自&#xff1a;尚硅谷-韩老师 尚硅谷 1&#xff09;函数的形参列表可以是多个&#xff0c;返回值列表也可以是多个 2&#xff09;形参列表和返回值列表的数据类型可以是值类型和引用类型 3&#xff09;函数的命名遵循标识符命名规范&#xff0c;首字母不能是数字&#xf…

京东购物成功订单已开具个人发票不能报销怎么申请更换重新开具企业发票用于报销?

原文来源&#xff1a;https://www.caochai.com/article-4109.html 京东购物订单的发票开成个人了可以更换成企业发票吗&#xff1f; 可以&#xff0c;通过京东购物完成的订单默认开个人发票&#xff0c;如果对发票有要求需要开企业发票的可以申请更换重新开企业发票&#xff1…

一文读懂JVM虚拟机:JVM虚拟机的内存管理(万字详解)

JVM虚拟机的内存管理 文章目录JVM虚拟机的内存管理JVM与操作系统Java虚拟机规范和 Java 语言规范的关系java虚拟机的内存管理JVM整体架构一、PC 程序计数器二、虚拟机栈三、本地方法栈四、堆Java 堆概念年轻代和老年代对象分配过程堆GC元空间为什么要废弃永久代&#xff0c;引入…

动手学习深度学习-《矩阵运算》

标量导数 常用求导&#xff1a; yyyaaaxnx^nxnexp(x)exp(x)exp(x)log(x)log(x)log(x)sin(x)sin(x)sin(x)dydx\frac{dy}{dx}dxdy​000nxn−1nx^{n-1}nxn−1exp(x)1x\frac{1}{x}x1​cos(x)cos(x)cos(x) 求导公式&#xff1a; yyyuvuvuvuvuvuvyf(u),ug(x)yf(u),ug(x)yf(u),ug(x)d…

植物大战僵尸:学会使用人造指针

通过向游戏中注入一段特殊的汇编代码&#xff0c;实现自动获取动态地址&#xff0c;省略找基址的麻烦。该方法适用于游戏基址层数过多无法直接获取到基址&#xff0c;游戏根本无法找到基址。 1.打开CE工具并附加游戏进程&#xff0c;首先通过遍历的方式找到阳光的动态地址&…

zookeeper之master选举代码实现

master选举的基本概念 &#xff08;1&#xff09;假设有一个系统A,它向外提供了一个服务&#xff0c;叫做服务B。并且这个服务需要24小时持续不断的向外提供。也就是提供服务的机器不能够有单点故障。于是我们考虑使用集群。 &#xff08;2&#xff09;我们采用的是master-sla…

傻白入门芯片设计,如何做文献笔记(十九)

Article: 文献出处&#xff08;方便再次搜索&#xff09; 作者文献题目文献时间Data: 文献数据&#xff08;总结归纳&#xff0c;方便理解&#xff09; 这篇文章的目的结论背景介绍结果方法&#xff08;可选&#xff09;Comments: 对文献的想法 &#xff08;强迫自己思考&#…