SpringBoot源码修炼—系统初始化器
SpringBoot源码修炼—系统初始化器
传统SSM框架与SpringBoot框架简要对比
SSM搭建流程
缺点:
- 耗时长
- 配置文件繁琐
- 需要找合适版本的jar包
SpringBoot搭建流程
优点:
- 耗时短
- 配置文件简洁
- 不关注版本管理
一、系统初始化器实践
- 类名: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文件中有这个初始化器,在加载系统初始化器的时候被加载。 
三种方式实现初始化器的原理
- 定义在spring.factories 文件中被SpringFactoriesLoader发现注册
- 初始化完毕后手动添加
- 定义成环境变量被DelegatingApplicationContextInitializer发现注册
SpringBoot源码修炼—系统初始化器的更多相关文章
- 从SpringBoot源码分析 配置文件的加载原理和优先级
本文从SpringBoot源码分析 配置文件的加载原理和配置文件的优先级 跟入源码之前,先提一个问题: SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数( ...
- springboot源码解读
springboot源码从main函数开始 public static void main(String[] args) { ApplicationContext app = SpringApplic ...
- SpringBoot源码分析之SpringBoot的启动过程
SpringBoot源码分析之SpringBoot的启动过程 发表于 2017-04-30 | 分类于 springboot | 0 Comments | 阅读次数 SpringB ...
- SpringBoot源码篇:深度分析SpringBoot如何省去web.xml
一.前言 从本博文开始,正式开启Spring及SpringBoot源码分析之旅.这可能是一个漫长的过程,因为本人之前阅读源码都是很片面的,对Spring源码没有一个系统的认识.从本文开始我会持续更新, ...
- Vue源码探究-状态初始化
Vue源码探究-状态初始化 Vue源码探究-源码文件组织 Vue源码探究-虚拟DOM的渲染 本篇代码位于vue/src/core/instance/state.js 继续随着核心类的初始化展开探索其他 ...
- SpringBoot源码学习系列之异常处理自动配置
SpringBoot源码学习系列之异常处理自动配置 1.源码学习 先给个SpringBoot中的异常例子,假如访问一个错误链接,让其返回404页面 在浏览器访问: 而在其它的客户端软件,比如postm ...
- SpringBoot源码学习系列之嵌入式Servlet容器
目录 1.博客前言简单介绍 2.定制servlet容器 3.变换servlet容器 4.servlet容器启动原理 SpringBoot源码学习系列之嵌入式Servlet容器启动原理 @ 1.博客前言 ...
- 外部配置属性值是如何被绑定到XxxProperties类属性上的?--SpringBoot源码(五)
注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 SpringBoot是如何实现自动配置的?--SpringBoot源码(四) 温故而知新,我们来简单回顾一下上 ...
- 如何分析SpringBoot源码模块及结构?--SpringBoot源码(二)
注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 如何搭建自己的SpringBoot源码调试环境?--SpringBoot源码(一). 前面搭建好了自己本地的S ...
随机推荐
- 国产smartbits版本-minismb测试高恪路由器IP限速
Minismb测试仪表是复刻smartbits的国产版本,是一款专门用于测试智能路由器,网络交换机的性能和稳定性的软硬件相结合的工具.可以通过此工具测试任何ip网络设备的端口吞吐率,带宽,并发连接数和 ...
- RSA 加密解密使用实例
http://www.dtmao.cc/news_show_692109.shtml 本文不讨论RSA加密解密本身,只记录使用方法及遇到的坑,RSA原理及注意事项可在网上查找. 背景:公司的一个需求, ...
- 数据库之postgreSQL入门操作指南
一.增 二.删 三.改 四.查 五.SQL操作表 1.增加列 ALTER TABLE table_name ADD column_name datatype; 2.删除一列 ALTER TABLE t ...
- ++i和i++的区别
它们两个的数值变化的区别,我这里就不多说了 这里主要说明两者在效率上的区别 (1)首先如果是自带的数据类型,比如int型,++i和i++,编译器的实现方式是相同的,两者并没有效率上的区别,虽然也有副本 ...
- JVM进阶篇
class Person { private String name = "Jack"; private int age; private final double salar ...
- 记一次getshell
水文涉及的知识点: Oday的挖掘 可以执行命令,但是有WAF , 命令执行的绕过 机器不出网,无法反弹 Echo写文件,发现只要写入php文件,后缀就重名为*,如1.php 变成1.* 通过上传 l ...
- 谈一谈phar 反序列化
前言 来自Secarma的安全研究员Sam Thomas发现了一种新的漏洞利用方式,可以在不使用php函数unserialize()的前提下,引起严重的php对象注入漏洞.这个新的攻击方式被他公开在了 ...
- 九种姿势运行Mimikatz
https://www.freebuf.com/articles/web/176796.html
- React Native & Fast Refresh
React Native & Fast Refresh 0.61 https://reactnative.dev/blog/2019/09/18/version-0.61/ Fast Refr ...
- css 命名冲突 & solution
css 命名冲突 & solution 类似这样,为了解决模块间可能存在的 css 命名冲突问题,需要单独提供给模块开发者一套模块开发环境:同时,文档上要有如何使用的规范说明. CSS 建议: ...