一、 ApplicationContextInitializer 介绍

  首先看spring官网的介绍:

  翻译一下:

  • 用于在spring容器刷新之前初始化Spring ConfigurableApplicationContext的回调接口。(剪短说就是在容器刷新之前调用该类的 initialize 方法。并将 ConfigurableApplicationContext 类的实例传递给该方法)
  • 通常用于需要对应用程序上下文进行编程初始化的web应用程序中。例如,根据上下文环境注册属性源或激活配置文件等。
  • 可排序的(实现Ordered接口,或者添加@Order注解)

  看完这段解释,为了讲解方便,我们先看自定义 ApplicationContextInitializer 的三种方式。再通过SpringBoot的源码,分析生效的时间以及实现的功能等。

二、三种实现方式

  首先新建一个类 MyApplicationContextInitializer 并实现 ApplicationContextInitializer 接口。

 public class MyApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("-----MyApplicationContextInitializer initialize-----");
}
}

  2.1、mian函数中添加

  优雅的写一个SpringBoot的main方法

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

  运行,查看控制台:生效了

  

  2.2、配置文件中配置

context.initializer.classes=org.springframework.boot.demo.common.MyApplicationContextInitializer 

  

  2.3、SpringBoot的SPI扩展---META-INF/spring.factories中配置

org.springframework.context.ApplicationContextInitializer=org.springframework.boot.demo.common.MyApplicationContextInitializer

  

三、排序问题

  如图所示改造一下mian方法。打一个断点,debug查看排序情况。

  

  给 MyApplicationContextInitializer 加上Order注解:我们指定其拥有最高的排序级别。(越高越早执行)

 @Order(Ordered.HIGHEST_PRECEDENCE)
public class MyApplicationContextInitializer implements ApplicationContextInitializer{
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("-----MyApplicationContextInitializer initialize-----");
}
}

  下面我们通过debug分别验证二章节中提到的三种方法排序是否都是可以的。

  首先验证2.1章节中采用的main函数中添加:debug,断点处查看 application.getInitializers() 这行代码的结果可见,排序生效了。

  

  然后再分别验证2.2和2.3章节中的方法。排序都是可以实现的。

  然而当采用2.3中的SPI扩展的方式,排序指定 @Order(Ordered.LOWEST_PRECEDENCE) 排序并没有生效。当然采用实现Ordered接口的方式,排序验证结果都是一样的。

四、通过源码分析ApplicationContextInitializer何时被调用

  debug差看上文中自定的 MyApplicationContextInitializer 的调用栈。

  

  可见 ApplicationContextInitializer 在容器刷新前的准备阶段被调用。 refreshContext(context);

  在SpringBoot的启动函数中, ApplicationContextInitializer

     public ConfigurableApplicationContext run(String... args) {
//记录程序运行时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// ConfigurableApplicationContext Spring 的上下文
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//从META-INF/spring.factories中获取监听器
//1、获取并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//2、构造容器环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//处理需要忽略的Bean
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
///3、初始化容器
context = createApplicationContext();
//实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[]{ConfigurableApplicationContext.class}, context);
//4、刷新容器前的准备阶段
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//5、刷新容器
refreshContext(context);
//刷新容器后的扩展接口
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
} try {
listeners.running(context);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

  然后看在 refreshContext(context); 具体是怎么被调用的。

 private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
...
}

  然后在 applyInitializers 中遍历调用每一个被加载的 ApplicationContextInitializer 的  initialize(context);  方法,并将 ConfigurableApplicationContext 的实例传递给 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);
}
}

  OK,到这里通过源码说明了 ApplicationContextInitializer 是何时及如何被调用的。

SpringBoot之ApplicationContextInitializer的理解和使用的更多相关文章

  1. ApplicationContextInitializer的理解和使用

    一.ApplicationContextInitializer 介绍 1.1 作用 ApplicationContextInitializer 接口用于在 Spring 容器刷新之前执行的一个回调函数 ...

  2. 对SpringBoot和SpringCloud的理解

    1.SpringCloud是什么 SpringCloud基于SpringBoot提供了一整套微服务的解决方案,包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,除了基于Net ...

  3. SpringBoot启动流程分析(一):SpringApplication类初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  4. SpringBoot集成Socket服务后打包(war包)启动时如何启动Socket服务(web应用外部tomcat启动)

      1.首先知道SpringBoot打包为jar和war包是不一样的(只讨论SpringBoot环境下web应用打包)     1.1.jar和war包的打开方式不一样,虽然都依赖java环境,但是j ...

  5. 很详细的SpringBoot整合UEditor教程

    很详细的SpringBoot整合UEditor教程 2017年04月10日 20:27:21 小宝2333 阅读数:21529    版权声明:本文为博主原创文章,未经博主允许不得转载. https: ...

  6. SpringBoot AOP 与 IoC

    Spring的核心就是AOP与IoC,想要学习SpringBoot,首先得理解这些概念: AOP(Aspect Oriented Programming 面向切面编程) IoC(Inversion o ...

  7. (一)SpringBoot入门【基于2.x版本】

    SpringBoot入门[基于2.x版本] 一.SpringBoot简介 首先大家学习SpringBoot的话,我希望大家是有一定java基础的,如果是有Spring的基础的话,上手会更加得心应手,因 ...

  8. SpringBoot基础篇-SpringBoot快速入门

    SpringBoot基础 学习目标: 能够理解Spring的优缺点 能够理解SpringBoot的特点 能够理解SpringBoot的核心功能 能够搭建SpringBoot的环境 能够完成applic ...

  9. MacOS下SpringBoot基础学习

    学于黑马和传智播客联合做的教学项目 感谢 黑马官网 传智播客官网 微信搜索"艺术行者",关注并回复关键词"springboot"获取视频和教程资料! b站在线视 ...

随机推荐

  1. 简单的邮件发送mail.jar

    public class MailSender { final static Logger logger = Logger.getLogger(MailSender.class); /** * 发送简 ...

  2. C#传递数组参数

    在C#中,可以将数组作为参数传递给方法,同时方法可以更改数组元素的值. 一.将一维数组作为参数传递给方法 using System;using System.Collections.Generic;u ...

  3. 重温经典之赫夫曼(Huffman)编码

    先看看赫夫曼树假设有n个权值{w1,w2,…,wn},构造一个有n个叶子结点的二叉树,每个叶子结点权值为wi,则其中带权路径长度WPL最小的二叉树称作赫夫曼树或最优二叉树. 赫夫曼树的构造,赫夫曼最早 ...

  4. NOIP模拟赛 准考证号

    准考证号 128M 0.1s ticket.cpp escription 蒟蒻hzwer NOIP2014惨跪,他依稀记得他的准考证号是37,现在hzwer又将要面临一场比赛,他希望准考证号不出现37 ...

  5. XML 转 fastJSON

      import java.util.List; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.Doc ...

  6. Vim如何显示和关闭行号

    显示行号: set nu 去除行号: set nonu

  7. java实现验证码功能

    java实现验证码功能 通过java代码实现验证码功能的一般思路: 一.通过java代码生成一张验证码的图片,将验证码的图片保存到项目中的指定文件中去,代码如下: package com.util; ...

  8. charles抓手机包

    charles抓手机包   如果是使用charles抓包.一定要tm的保证手机和电脑连的是一个网.   1.proxy setting,查看charles,端口 2.勾选   3.ipconfig,查 ...

  9. python-面试常用 --变量、内存管理(小整数池,引用计数)

    执行Python程序的两种方法 第一种:交互式(jupyter就是对这种进行了封装) 优点:直接给出结果 缺点:无法保存 第二种:命令行式,通过Python解释器输入文本(pycharm对这种进行了封 ...

  10. 设置vim 永久显示行号

    永久显示行号:如果想让vim永久显示行号,则需要修改vim配置文件vimrc.如果没有此文件可以创建一个.在启动vim时,当前用户根目录下的vimrc文件会被自动读取,因此一般在当前用户的根目录下创建 ...