SpringBoot源码修炼—系统初始化器

传统SSM框架与SpringBoot框架简要对比

SSM搭建流程 缺点:

  1. 耗时长
  2. 配置文件繁琐
  3. 需要找合适版本的jar包

SpringBoot搭建流程 优点

  1. 耗时短
  2. 配置文件简洁
  3. 不关注版本管理

一、系统初始化器实践

  • 类名:ApplicationContextInitializer
  • 介绍:Spring容器刷新之前执行的一个回调函数
  • 作用:向SpringBoot容器中注册属性
  • 使用:继承接口自定义实现

创建系统初始化器方式一

(1)创建初始化器(在包initializer下创建FirstInitializer)

/**
 *
 * 第一个系统初始化器
 */
@Order(1)
public class FirstInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {

        ConfigurableEnvironment environment = applicationContext.getEnvironment();

        Map<String, Object> map = new HashMap<>();
        map.put("key1", "value1");

        MapPropertySource mapPropertySource = new MapPropertySource("firstInitializer", map);

        environment.getPropertySources().addLast(mapPropertySource);

        System.out.println("run firstInitializer");

    }
}

(2)创建spring.factories 内容为:org.springframework.context.ApplicationContextInitializer=com.mooc.springboot2.initializer.FirstInitializer

(3)创建服务类TestService

@Component
public class TestService implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public String test() {
        return applicationContext.getEnvironment().getProperty("key1");
    }

}

(4)在Controller添加测试方法调用

@Controller
@RequestMapping("/demo")
public class DemoController {
    @Autowired
    private TestService testService;

    @RequestMapping("test")
    @ResponseBody
    public String test() {
        return testService.test();
    }
}

(5)启动工程查看效果 返回值是之前设置的value1 创建系统初始化器方式二

(1)创建初始化器

@Order(2)
public class SecondInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {

        ConfigurableEnvironment environment = applicationContext.getEnvironment();

        Map<String, Object> map = new HashMap<>();
        map.put("key2", "value2");

        MapPropertySource mapPropertySource = new MapPropertySource("secondInitializer", map);

        environment.getPropertySources().addLast(mapPropertySource);

        System.out.println("run secondInitializer");
    }
}

(2)启动类修改

@SpringBootApplication
@MapperScan("com.mooc.demo.mapper")
public class Springboot2Application{
 
    public static void main(String[] args) {
        //SpringApplication.run(Springboot2Application.class, args);
        SpringApplication springApplication = new SpringApplication(Springboot2Application.class);
        springApplication.addInitializers(new SecondInitializer());
        springApplication.run(args);
    }
}

(3) 修改TestService的test方法

public String test(){
     return  applicationContext.getEnvironment().getProperty("key2");
 }

(4)启动工程查看效果 说明两个初始化器都已经创建了 返回值为value2 创建系统初始化器方式三

(1)创建初始化器

@Order(3)
public class ThirdInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {

        ConfigurableEnvironment environment = applicationContext.getEnvironment();

        Map<String, Object> map = new HashMap<>();
        map.put("key3", "value3");

        MapPropertySource mapPropertySource = new MapPropertySource("thirdInitializer", map);

        environment.getPropertySources().addLast(mapPropertySource);

        System.out.println("run thirdInitializer");
    }
}

(2)在application.properties 添加

context.initializer.classes=com.example.demo.initializer.ThirdInitializer

(3) 修改TestService的test方法

public String test(){
     return applicationContext.getEnvironment().getProperty("key3");
 }

(4)启动工程查看效果 可以发现打印的顺序和定义的不一致,第三个ThirdInitializer初始化器优先于第一个和第二个打印出来。

返回值为value3 springboot初始化器是如何被识别的呢?

本质上靠的就是SpringFactoriesLoader

二、SpringFactoriesLoader 介绍

框架内部使用的通用工厂加载机制 从classpath下多个jar包特定的位置读取文件并初始化类 文件内容必须是kv形式,即properties类型 key是全限定名(抽象类|接口)、value是实现,多个实现用逗号分隔

作用:SpringBoot框架从类路径所有jar包中读取特定文件实现扩展类的载入

查看源码

从启动类进入

SpringApplication.run(Sb2Application.class, args);

1、进入run方法

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}
 
     
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}
     
public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

2、进入SpringApplication构造函数

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();
}

3、进入setInitializers方法

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

通过getSpringFactoriesInstances获取系统初始化器的一些实现

4、进入getSpringFactoriesInstances方法 通过loadFactoryNames这个方法去获得所有系统初始化器实现类的全路径名,再通过createSpringFactoriesInstances去获得它们的实例,接着通过AnnotationAwareOrderComparator对它们进行排序。

5、进入loadFactoryNames方法

6、进入loadSpringFactories方法 可以发现FACTORIES_RESOURCE_LOCATION的值为"META-INF/spring.factories"。 这就是为什么我们前面要在META-INF文件夹下创建spring.factories 文件的原因。

获得所有Jar包下的META-INF/spring.factories文件

通过以上过程,将初始化器实现的类名注入到容器当中 如下图,debug显示所有系统初始化器

7、实例化这些初始化器

8、通过setInitializers将初始化器实例注入到spring容器中

loadSpringFactories整体流程

三、系统初始化器原理

探究系统初始化器是如何被调用以及被调用的原理

经过对官方的翻译,它的意思大致如下:

  • 上下文刷新即spring的refresh方法前调用
  • 用来编码设置一些属性变量通常用在web环境中
  • 可以通过order接口进行排序

springboot大致流程如下图所示,其中在准备上下文过程中,会遍历调用Initalizer的initalize方法

系统初始化器方式一实现原理

进入run方法查看源码

再进入准备上下文的方法prepareContext中,可以看到调用初始化器部分

再次进入,这里遍历所有系统初始化器,并调用对应的初始化器的initialize方法( getInitializers返回所有的系统初始化器)

系统初始化器方式二实现原理

这里通过new一个SpringApplication,添加我们自定义的初始化器

我们知道在SpringApplication初始化之后,系统初始化器已经设置过了(通过setInitializers方法)

同时SpingApplication实例提供了addInitializers方法来帮助我们增加自定义的初始化器 再通过springApplication.run(args)的run方法(和方式一的run方法是同一个)就能保证添加的系统初始化器能够在后面的run方法中正常执行

系统初始化器方式三实现原理

通过在application.properties 配置文件中添加配置context.initializer.classes=com.mooc.demo.initializer.ThirdInitializer来实现

这里主要是通过DelegatingApplicationContextInitializer初始化器来实现的 可以看到DelegatingApplicationContextInitializer里的order=0,也就是说这个初始化器最先被调用

在spring的spring.factories文件中有这个初始化器,在加载系统初始化器的时候被加载。

三种方式实现初始化器的原理

  1. 定义在spring.factories 文件中被SpringFactoriesLoader发现注册
  2. 初始化完毕后手动添加
  3. 定义成环境变量被DelegatingApplicationContextInitializer发现注册

SpringBoot源码修炼—系统初始化器的更多相关文章

  1. 从SpringBoot源码分析 配置文件的加载原理和优先级

    本文从SpringBoot源码分析 配置文件的加载原理和配置文件的优先级     跟入源码之前,先提一个问题:   SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数( ...

  2. springboot源码解读

    springboot源码从main函数开始 public static void main(String[] args) { ApplicationContext app = SpringApplic ...

  3. SpringBoot源码分析之SpringBoot的启动过程

    SpringBoot源码分析之SpringBoot的启动过程 发表于 2017-04-30   |   分类于 springboot  |   0 Comments  |   阅读次数 SpringB ...

  4. SpringBoot源码篇:深度分析SpringBoot如何省去web.xml

    一.前言 从本博文开始,正式开启Spring及SpringBoot源码分析之旅.这可能是一个漫长的过程,因为本人之前阅读源码都是很片面的,对Spring源码没有一个系统的认识.从本文开始我会持续更新, ...

  5. Vue源码探究-状态初始化

    Vue源码探究-状态初始化 Vue源码探究-源码文件组织 Vue源码探究-虚拟DOM的渲染 本篇代码位于vue/src/core/instance/state.js 继续随着核心类的初始化展开探索其他 ...

  6. SpringBoot源码学习系列之异常处理自动配置

    SpringBoot源码学习系列之异常处理自动配置 1.源码学习 先给个SpringBoot中的异常例子,假如访问一个错误链接,让其返回404页面 在浏览器访问: 而在其它的客户端软件,比如postm ...

  7. SpringBoot源码学习系列之嵌入式Servlet容器

    目录 1.博客前言简单介绍 2.定制servlet容器 3.变换servlet容器 4.servlet容器启动原理 SpringBoot源码学习系列之嵌入式Servlet容器启动原理 @ 1.博客前言 ...

  8. 外部配置属性值是如何被绑定到XxxProperties类属性上的?--SpringBoot源码(五)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 SpringBoot是如何实现自动配置的?--SpringBoot源码(四) 温故而知新,我们来简单回顾一下上 ...

  9. 如何分析SpringBoot源码模块及结构?--SpringBoot源码(二)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 如何搭建自己的SpringBoot源码调试环境?--SpringBoot源码(一). 前面搭建好了自己本地的S ...

随机推荐

  1. 国产网络测试仪MiniSMB - 如何3秒内创建出16,000条UDP/TCP端口号递增流

    国产网络测试仪MiniSMB(www.minismb.com)是复刻smartbits的IP网络性能测试工具,是一款专门用于测试智能路由器,网络交换机的性能和稳定性的软硬件相结合的工具.可以通过此以太 ...

  2. Web安全之SQL注入(原理,绕过,防御)

    首先了解下Mysql表结构 mysql内置的information_schema数据库中有三个表非常重要1 schemata:表里包含所有数据库的名字2 tables:表里包含所有数据库的所有的表,默 ...

  3. 51nod 1384 可重集的全排列

    对于1231,121,111等有重复的数据,我们怎么做到生成全排列呢 实际上,对于打标记再释放标记的这种方法,如果一开始第一层递归访问过1那么你再访问 就会完全重复上一次1开头的情况,那么递归地考虑这 ...

  4. 如何快速定位 Redis 热 key?

    背景 在 Redis 中,热 key 指的是那些在一段时间内访问频次比较高的键值,具体到业务上,商品的限时抢购.瞬时的新闻热点或某个全局性的资源,都极有可能产生热点 key. 热点 key 的出现可能 ...

  5. 高阶函数 HOF & 高阶组件 HOC

    高阶函数 HOF & 高阶组件 HOC 高阶类 js HOC 高阶函数 HOF 函数作为参数 函数作为返回值 "use strict"; /** * * @author x ...

  6. how to convert SVG shapes to polygon

    how to convert SVG shapes to polygon 如何将 svg 的 rect 转换成 polygon rect.circle.ellipse.line.polyline.po ...

  7. NGK DeFi Baccarat或将推动BGV成为下一个千倍币!

    目前,已经接近2020年年末,但是DeFi的热潮还在持续.近日,DeFi市场传出一道重磅利好消息,便是NGK DeFi去中心化交易系统Baccarat即将上线.届时,将会引起整个区块链市场的又一次震动 ...

  8. 为什么要抢挖Baccarat流动性挖矿的头矿?头矿的价值是什么?

    今年下半年,DeFi流动性挖矿非常受投资者的欢迎,究其原因,其超高的挖矿回报率着实足够吸引无数投资者的眼球.而即将上线的Baccarat流动性挖矿,也未上线先火了一把.Baccarat是由NGK公链推 ...

  9. django学习-26.admin管理后台里:修改登录页面标题,修改登录框标题,修改首页标题

    目录结构 1.前言 2.完整的操作步骤 2.1.第一步:查看[site.py]的源码 2.2.第二步:在应用[hello]所在目录里的[admin.py]里重写三个属性的属性值 2.3.第三步:重启服 ...

  10. java中的桥接方法

    本文转载自java中什么是bridge method(桥接方法) 导语 在看spring-mvc的源码的时候,看到在解析handler方法时,有关于获取桥接方法代码,不明白什么是桥接方法,经过查找资料 ...