springboot启动流程(六)ioc容器刷新前prepareContext
所有文章
https://www.cnblogs.com/lay2017/p/11478237.html
prepareContext方法核心逻辑
上一篇文章中,我们通过createApplicationContext方法创建了一个ApplicationContext的实例对象。本文将阅读一下在ApplicationContext在refresh之前的prepareContext中做了哪些事情。
我们跟进prepareContext方法
private void prepareContext(
ConfigurableApplicationContext context,
ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments,
Banner printedBanner
) {
// 设置Environment
context.setEnvironment(environment);
// 后置处理
postProcessApplicationContext(context);
// 调用ApplicationContextInitializer接口的实现
applyInitializers(context);
// 发布ApplicationContext准备事件
listeners.contextPrepared(context); ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 注册args参数为单例bean
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
// 注册banner为单例bean
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
// 设置beanFactory中是否允许重复bean覆盖
((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
} // 加载main方法所在类
Set<Object> sources = getAllSources();
// 注册main方法所在类到beanFactory
load(context, sources.toArray(new Object[0]));
// 发布Context加载事件
listeners.contextLoaded(context);
}
我们看到prepareContext方法主要逻辑包含了三块内容
1)基本的初始化,如设置Environment,调用ApplicationContextInitializer接口的实现类
2)注册现有的对象为单例bean,如args、banner
3)加载main方法所在的主类
load方法加载主类
很显然,加载main方法的主类是我们关注的重点。我们先看看getAllSources方法返回
public Set<Object> getAllSources() {
Set<Object> allSources = new LinkedHashSet<>();
// 添加主类
if (!CollectionUtils.isEmpty(this.primarySources)) {
allSources.addAll(this.primarySources);
}
// 添加附加资源类
if (!CollectionUtils.isEmpty(this.sources)) {
allSources.addAll(this.sources);
}
return Collections.unmodifiableSet(allSources);
}
primarySources是在SpringApplication初始化的时候设置的,而sources默认是没有的。所在getAllSoures方法将返回main方法所在的主类。
下面,我们跟进load方法,阅读一下加载main所在主类的逻辑
protected void load(ApplicationContext context, Object[] sources) {
// 获取BeanDefinition加载器
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
// 加载资源
loader.load();
}
load方法中,先是获得了BeanDefinitionLoader,然后去加载资源。这里要注意!为什么是BeanDefinitionLoader呢?
首先,我们得知道Spring的bean资源来自各种地方,如xml、annotation等,那么这些bean在配置的时候也就是对bean进行定义,而这些定义映射到内存中的对象就是BeanDefinition的对象,Spring后续会根据BeanDefinition再获取具体的bean。
简单来说就是:配置 --> BeanDefinition --> Bean 这样一个逻辑
所以,后续我们会看到BeanDefinitionLoader会将主类加载成BeanDefinition,然后注册到ApplicationContext当中。
先跟进getBeanDefinitionRegistry方法,看看BeanDefinition会被注册到哪里去
private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
// 返回当前ApplicationContext
if (context instanceof BeanDefinitionRegistry) {
return (BeanDefinitionRegistry) context;
}
if (context instanceof AbstractApplicationContext) {
return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
}
throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
}
我们注意,springboot的AnnotationConfigServletWebServerApplicationContext这个ApplicationContext的实现类,随着继承链路向上走是继承自GenericApplicationContext的,而GenericApplicationContext实现了BeanDefinitionRegistry。
所以,getBeanDefinitionRegistry将最终返回强转过的ApplicationContext。也就是说BeanDefinition将被注册到ApplicationContext里面。
回到load方法中,我们再跟进createBeanDefinitionLoader方法
protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
return new BeanDefinitionLoader(registry, sources);
}
再跟进构造方法
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
this.sources = sources;
// 注解方式的读取器
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
// xml方式的读取器
this.xmlReader = new XmlBeanDefinitionReader(registry); // 类路径下的扫描器
this.scanner = new ClassPathBeanDefinitionScanner(registry);
// 扫描排除当前main方法的主类
this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}
我们看到加载器支持注解、xml两种方式。类路径下的扫描器排除了当前的主类
回到load方法
protected void load(ApplicationContext context, Object[] sources) {
// 获取BeanDefinition加载器
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
// 加载资源
loader.load();
}
此时,我们已经获取了BeanDefinitionLoader,下面调用该loader的load方法开始加载
跟进第二个load方法
public int load() {
int count = 0;
for (Object source : this.sources) {
count += load(source);
}
return count;
}
再跟进第三个load方法
private int load(Object source) {
if (source instanceof Class<?>) {
return load((Class<?>) source);
}
if (source instanceof Resource) {
return load((Resource) source);
}
if (source instanceof Package) {
return load((Package) source);
}
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
由于我们的主类是一个class,所以进入第一个if分支的load方法
继续跟进
private int load(Class<?> source) {
// 省略
if (isComponent(source)) {
this.annotatedReader.register(source);
return 1;
}
return 0;
}
该方法先通过isComponent方法判断了主类是不是被@Component注解,如果是,那么调用注解方式的阅读器,注册该资源。
跟进isComponent方法,看看怎么判断的
private boolean isComponent(Class<?> type) {
// 找到是否匹配@Component注解
if (AnnotationUtils.findAnnotation(type, Component.class) != null) {
return true;
}
// 省略
}
其实就是找这个类是否有@Component注解,但请注意我们通常都使用@SpringBootApplication这个注解,并没有直接注解@Component。而@SpringBootApplication是一个组合注解,其中就组合了@Component
而AnnotationUtils.findAnnotation方法将会递归遍历注解,最终找到@Component。
isComponent判断为true以后,我们再跟进annotationReader.register(source)阅读一下读取主类的过程
public void register(Class<?>... annotatedClasses) {
for (Class<?> annotatedClass : annotatedClasses) {
registerBean(annotatedClass);
}
}
继续跟进registerBean方法
public void registerBean(Class<?> annotatedClass) {
doRegisterBean(annotatedClass, null, null, null);
}
再跟进doRegisterBean方法,该方法比较长,我们省略掉一些次要的部分
<T> void doRegisterBean(
Class<T> annotatedClass,
@Nullable Supplier<T> instanceSupplier,
@Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers,
BeanDefinitionCustomizer... definitionCustomizers
) {
// 先包装成BeanDefinition
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass); // 解析scope元数据
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName()); // 生成bean的名
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry)); // 解析一些常见的注解元数据
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd); BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// 注册BeanDefinition到ApplicationContext
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
可以看到,doRegisterBean方法的主要逻辑就是包装并解析出一个BeanDefinition,然后调用registerBeanDefinition方法把BeanDefinition给注册到ApplicationContext中。
注册相关的本文就不再继续展开了,后续的文章会跟进这些内容。
总结
总的来说,prepareContext方法主要就是为了加载并注册主类的BeanDefinition到ApplicationContext。这里注意!我们一直都在说注册到ApplicationContext,但熟悉spring的都会知道无论是Bean还是BeanDefinition都是注册到BeanFactory中的。但我们一直没有严格区分它,后续的文章我们将会把ApplicationContext和BeanFactory进行区分。
springboot启动流程(六)ioc容器刷新前prepareContext的更多相关文章
- springboot启动流程(目录)
springboot出现有段时间了,不过却一直没有怎么去更多地了解它.一方面是工作的原因,另一方面是原来觉得是否有这个必要,但要持续做java似乎最终逃不开要去了解它的命运.于是考虑花一段时间去学习一 ...
- SpringBoot启动流程分析(六):IoC容器依赖注入
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程分析(四):IoC容器的初始化过程
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- springboot启动流程(七)ioc容器refresh过程(上篇)
所有文章 https://www.cnblogs.com/lay2017/p/11478237.html 正文 在前面的几篇文章中,我们看到Environment创建.application配置文件的 ...
- SpringBoot启动流程分析(二):SpringApplication的run方法
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程分析(三):SpringApplication的run方法之prepareContext()方法
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程分析(五):SpringBoot自动装配原理实现
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程分析(一):SpringApplication类初始化过程
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程及其原理
Spring Boot.Spring MVC 和 Spring 有什么区别? 分别描述各自的特征: Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等:但他们的 ...
随机推荐
- uefi是如何启动linux内核的?
答:uefi启动linux内核有两条路径: 1. uefi直接进入uefi shell来启动linux内核 2. uefi直接进入uefi shell启动grub启动器,然后进入grub shell启 ...
- 打印变量地址-0x%08x
地址是8个16进制数. 1.8个16进制数:相当于32个二进制数.4G内存刚好可以用32位的二进制表示出来.2.因为变量或函数等等在运行时都是存储在内存中的,所以你用取地址符当然是取出计算机内存中的地 ...
- 论文翻译 DOTA:A Large-scale Dataset for Object Detection in Aerial Images
简介:武大遥感国重实验室-夏桂松和华科电信学院-白翔等合作做的一个航拍图像数据集 摘要: 目标检测是计算机视觉领域一个重要且有挑战性的问题.虽然过去的十几年中目标检测在自然场景已经有了较重要的成就 ...
- 设计-Int(4)和Int(11)谁更美
设计-Int(4)和Int(11)谁更美 [缘起] 大家平时在进行数据库设计的时候,如果遇到需要存储整数类型的数据的时候,通常会优先使用Int这个整数类型,在处理20亿级别的正负数值存储上,Int类型 ...
- DELPHI中 screen.Cursor:=crhourglass; adoQuery.close; adoquery.Open; screen.Cursor:=crdefault;啥意思
鼠标忙这段代码大概是用来演示鼠标的用法的.具体解释如下: 使鼠标指针为沙漏状.(以表示程序正忙)screen.Cursor:=crhourglass; 把(打开的)数据库关闭.adoQuery.clo ...
- MySQL高性能优化指导思路
MySQL架构图: 连接池组件.管理服务和工具组件.SQL接口组件.查询分析器组件.优化器组件.缓冲组件.插件式存储引擎.物理文件: 1.连接层:主要完成一些类似于连接处理,授权认证及相关的方案: 2 ...
- cut截取数据
参考文档 https://blog.csdn.net/caoshunxin01/article/details/79355566 [root@kube-node3 ~]# cat tab_space. ...
- localStorage 存储 数组
let str = JSON.stringify(data.list); localStorage.setItem("options",str); let optionss=loc ...
- Struts 2 --ONGL介绍
先了解一下OGNL的概念 OGNL的全名称Object Graph Navigation Language.全称为对象图导航语言,是一种表达式语言.使用这种表达式语言,你可以通过某种表达式语法,存取J ...
- URL锚点HTML定位技术机制、应用与问题
by zhangxinxu from http://www.zhangxinxu.com本文地址:http://www.zhangxinxu.com/wordpress/?p=3591 一.锚点是什么 ...