一、前言

  • Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去。
  • 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot源码管中窥豹系列。

二、Initializer

  • 上一节我们介绍了Runner,它是在项目加载完成之后执行的
  • 有后就有前,有没有在项目加载之前执行的呢?

我们今天介绍的ApplicationContextInitializer就是在spring的bean加载之前执行的

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

	void initialize(C applicationContext);

}
  • 使用很简单,实现ApplicationContextInitializer接口就可以了
  • 它是先于普通bean加载的,所以不能用@Component的方式
  • 具体怎么被springboot加载,往下看,我们分析源码的时候说

三、源码解析

如果对springboot源码一点都不了解的,推荐先看第一节:整体架构

1、获取ApplicationContextInitializer

我们直接先看SpringApplication的构造方法

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

    ...

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

    ...
}

我们先看setInitializers方法,再看里面的getSpringFactoriesInstances方法

public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>(initializers);
}
  • 很简单,把查询的initializers集合赋值到本地变量上
  • 接着看getSpringFactoriesInstances方法,这个initializers集合怎么查的

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
} private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
  • (1) 获取ApplicationContextInitializer实现类的名称集合
  • (2) 加载成实例instances
  • (3) 排序,返回

我们先研究下SpringFactoriesLoader.loadFactoryNames(type, classLoader)这个方法:


public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
} private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
} try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
  • 注意:factoryType就是我们传入的参数 ApplicationContextInitializer.class
  • 这里有个新的map结构:MultiValueMap<String, String>,它和Map<String, List>是一样的
  • 我们先看一下这里:classLoader.getResources(FACTORIES_RESOURCE_LOCATION)
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  • 加载所有的META-INF/spring.factories,按接口名称放入MultiValueMap<String, String>,并cache
  • 注意,这类文件不止一个,它们分布在不同的jar包里面
  • 这么说你可能不懂,我们看一下这类文件长什么样,我们看一个系统自带的
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader # Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener # Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers # Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer # Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener # Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor # Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer # FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
  • 接口 = 实现类1,实现类2,实现类3
  • 我们假如有了自己的ApplicationContextInitializer实现类,我们在resource下面新建/META-INF/spring.factories文件,按上面的格式写上就可以被加载了
org.springframework.context.ApplicationContextInitializer=\
org.my.zb.MyApplicationContextInitializer
  • 我们把思维拉回去,讲完了怎么取的实现类名称集合
  • 回去看 createSpringFactoriesInstances();
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
  • (1) 获取Class
  • (2) 获取构造函数
  • (3) 利用反射新建instance对象
  • (4) 加入集合

至此,我们就得到了:List instances

2、执行ApplicationContextInitializer

我们看SpringApplication的run方法:

public ConfigurableApplicationContext run(String... args) {

    ...

    try {

        ...

        prepareContext(context, environment, listeners, applicationArguments, printedBanner);

        ...

    }
catch (Throwable ex) {
... } ... return context;
}

进入到prepareContext方法:

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

定位到了applyInitializers():

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);
}
}
  • 前两行判断类型
  • 最后一行回调执行

欢迎关注公众号:丰极,更多技术学习分享。

springboot源码解析-管中窥豹系列之Initializer(四)的更多相关文章

  1. springboot源码解析-管中窥豹系列

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

  2. springboot源码解析-管中窥豹系列之BeanDefine如何加载(十三)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

  3. springboot源码解析-管中窥豹系列之BeanPostProcessor(十二)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

  4. springboot源码解析-管中窥豹系列之bean如何生成?(十四)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

  5. springboot源码解析-管中窥豹系列之web服务器(七)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

  6. springboot源码解析-管中窥豹系列之BeanDefinition(八)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

  7. springboot源码解析-管中窥豹系列之自动装配(九)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

  8. springboot源码解析-管中窥豹系列之EnableXXX(十)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

  9. springboot源码解析-管中窥豹系列之BeanFactoryPostProcessor(十一)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

随机推荐

  1. 在 GitHub 玩硬件——GitHub 热点速览 Vol.49

    作者:HelloGitHub-小鱼干 本周的 GitHub Trending 可以说是非常之硬核,天才少年稚晖君的 2 个硬件变装项目荣登热点榜,看完将充电宝改装为显示器的视频,搭配 HDMI-PI ...

  2. C#使用ML.Net完成人工智能预测

    前言 Visual Studio2019 Preview中提供了图形界面的ML.Net,所以,只要我们安装Visual Studio2019 Preview就能简单的使用ML.Net了,因为我的电脑已 ...

  3. Windows下MySQL无法启动万能解决方案

    MySQL启动报错 window10上安装了MySQL之前使用都是执行net start mysql 启动,执行net stop mysql关闭. 某天开始启动报错"MySQL 服务无法启动 ...

  4. 半夜删你代码队 Day6冲刺

    一.每日站立式会议 1.站立式会议 成员 昨日完成工作 今日计划工作 遇到的困难 陈惠霖 完成注册界面 好友界面 无 侯晓龙 了解数据库使用 帮助他人建立数据库 无 周楚池 完成登录界面+管理员界面初 ...

  5. Layui事件监听(表单和数据表格)

    一.表单的事件监听 先介绍一下几个属性的用法 1.lay-filter 事件过滤器 相当于选择器,layui的专属选择器 2.lay-verify 验证属性 属性值可以是 :required必填项, ...

  6. Salesforce LWC学习(二十八) 复制内容到系统剪贴板(clipboard)

    本篇参考: https://developer.mozilla.org/zh-CN/docs/Mozilla/Add-ons/WebExtensions/Interact_with_the_clipb ...

  7. 【Harbor学习笔记】-教你快速搭建Docker私有仓库

    目录 架构图 Harbor依赖的外部组件 Harbor自有组件 核心组件 安装 1. 下载离线安装包 2. 配置 harbor.cfg (harbor.yml) 3. 启动 Harbor 安装配置问题 ...

  8. JDK下载与安装

    Java有很多个版本,最新的版本会兼容之前的. 先附上下载地址:https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downlo ...

  9. SpringBoot进阶教程(六十七)RateLimiter限流

    在上一篇文章nginx限流配置中,我们介绍了如何使用nginx限流,这篇文章介绍另外一种限流方式---RateLimiter. v限流背景 在早期的计算机领域,限流技术(time limiting)被 ...

  10. DotNet .Net Framework与Net Core与Net Standard 以及.NET5

    Net Framework 是什么 1.Net Framework 是Net的一种实现,在此类库上我们可以使用C#,VB,F#进行程序编写,主要用于构建Windows 下的应用程序 2.有两部分组成部 ...