所有文章

https://www.cnblogs.com/lay2017/p/11478237.html

prepareContext方法核心逻辑

上一篇文章中,我们通过createApplicationContext方法创建了一个ApplicationContext的实例对象。本文将阅读一下在ApplicationContext在refresh之前的prepareContext中做了哪些事情。

我们跟进prepareContext方法

  1. private void prepareContext(
  2. ConfigurableApplicationContext context,
  3. ConfigurableEnvironment environment,
  4. SpringApplicationRunListeners listeners,
  5. ApplicationArguments applicationArguments,
  6. Banner printedBanner
  7. ) {
  8. // 设置Environment
  9. context.setEnvironment(environment);
  10. // 后置处理
  11. postProcessApplicationContext(context);
  12. // 调用ApplicationContextInitializer接口的实现
  13. applyInitializers(context);
  14. // 发布ApplicationContext准备事件
  15. listeners.contextPrepared(context);
  16.  
  17. ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
  18. // 注册args参数为单例bean
  19. beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
  20. if (printedBanner != null) {
  21. // 注册banner为单例bean
  22. beanFactory.registerSingleton("springBootBanner", printedBanner);
  23. }
  24. if (beanFactory instanceof DefaultListableBeanFactory) {
  25. // 设置beanFactory中是否允许重复bean覆盖
  26. ((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
  27. }
  28.  
  29. // 加载main方法所在类
  30. Set<Object> sources = getAllSources();
  31. // 注册main方法所在类到beanFactory
  32. load(context, sources.toArray(new Object[0]));
  33. // 发布Context加载事件
  34. listeners.contextLoaded(context);
  35. }

我们看到prepareContext方法主要逻辑包含了三块内容

1)基本的初始化,如设置Environment,调用ApplicationContextInitializer接口的实现类

2)注册现有的对象为单例bean,如args、banner

3)加载main方法所在的主类

load方法加载主类

很显然,加载main方法的主类是我们关注的重点。我们先看看getAllSources方法返回

  1. public Set<Object> getAllSources() {
  2. Set<Object> allSources = new LinkedHashSet<>();
  3. // 添加主类
  4. if (!CollectionUtils.isEmpty(this.primarySources)) {
  5. allSources.addAll(this.primarySources);
  6. }
  7. // 添加附加资源类
  8. if (!CollectionUtils.isEmpty(this.sources)) {
  9. allSources.addAll(this.sources);
  10. }
  11. return Collections.unmodifiableSet(allSources);
  12. }

primarySources是在SpringApplication初始化的时候设置的,而sources默认是没有的。所在getAllSoures方法将返回main方法所在的主类。

下面,我们跟进load方法,阅读一下加载main所在主类的逻辑

  1. protected void load(ApplicationContext context, Object[] sources) {
  2. // 获取BeanDefinition加载器
  3. BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
  4. if (this.beanNameGenerator != null) {
  5. loader.setBeanNameGenerator(this.beanNameGenerator);
  6. }
  7. if (this.resourceLoader != null) {
  8. loader.setResourceLoader(this.resourceLoader);
  9. }
  10. if (this.environment != null) {
  11. loader.setEnvironment(this.environment);
  12. }
  13. // 加载资源
  14. loader.load();
  15. }

load方法中,先是获得了BeanDefinitionLoader,然后去加载资源。这里要注意!为什么是BeanDefinitionLoader呢?

首先,我们得知道Spring的bean资源来自各种地方,如xml、annotation等,那么这些bean在配置的时候也就是对bean进行定义,而这些定义映射到内存中的对象就是BeanDefinition的对象,Spring后续会根据BeanDefinition再获取具体的bean。

简单来说就是:配置 --> BeanDefinition --> Bean 这样一个逻辑

所以,后续我们会看到BeanDefinitionLoader会将主类加载成BeanDefinition,然后注册到ApplicationContext当中。

先跟进getBeanDefinitionRegistry方法,看看BeanDefinition会被注册到哪里去

  1. private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
  2. // 返回当前ApplicationContext
  3. if (context instanceof BeanDefinitionRegistry) {
  4. return (BeanDefinitionRegistry) context;
  5. }
  6. if (context instanceof AbstractApplicationContext) {
  7. return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
  8. }
  9. throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
  10. }

我们注意,springboot的AnnotationConfigServletWebServerApplicationContext这个ApplicationContext的实现类,随着继承链路向上走是继承自GenericApplicationContext的,而GenericApplicationContext实现了BeanDefinitionRegistry。

所以,getBeanDefinitionRegistry将最终返回强转过的ApplicationContext。也就是说BeanDefinition将被注册到ApplicationContext里面。

回到load方法中,我们再跟进createBeanDefinitionLoader方法

  1. protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
  2. return new BeanDefinitionLoader(registry, sources);
  3. }

再跟进构造方法

  1. BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
  2. this.sources = sources;
  3. // 注解方式的读取器
  4. this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
  5. // xml方式的读取器
  6. this.xmlReader = new XmlBeanDefinitionReader(registry);
  7.  
  8. // 类路径下的扫描器
  9. this.scanner = new ClassPathBeanDefinitionScanner(registry);
  10. // 扫描排除当前main方法的主类
  11. this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
  12. }

我们看到加载器支持注解、xml两种方式。类路径下的扫描器排除了当前的主类

回到load方法

  1. protected void load(ApplicationContext context, Object[] sources) {
  2. // 获取BeanDefinition加载器
  3. BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
  4. if (this.beanNameGenerator != null) {
  5. loader.setBeanNameGenerator(this.beanNameGenerator);
  6. }
  7. if (this.resourceLoader != null) {
  8. loader.setResourceLoader(this.resourceLoader);
  9. }
  10. if (this.environment != null) {
  11. loader.setEnvironment(this.environment);
  12. }
  13. // 加载资源
  14. loader.load();
  15. }

此时,我们已经获取了BeanDefinitionLoader,下面调用该loader的load方法开始加载

跟进第二个load方法

  1. public int load() {
  2. int count = 0;
  3. for (Object source : this.sources) {
  4. count += load(source);
  5. }
  6. return count;
  7. }

再跟进第三个load方法

  1. private int load(Object source) {
  2. if (source instanceof Class<?>) {
  3. return load((Class<?>) source);
  4. }
  5. if (source instanceof Resource) {
  6. return load((Resource) source);
  7. }
  8. if (source instanceof Package) {
  9. return load((Package) source);
  10. }
  11. if (source instanceof CharSequence) {
  12. return load((CharSequence) source);
  13. }
  14. throw new IllegalArgumentException("Invalid source type " + source.getClass());
  15. }

由于我们的主类是一个class,所以进入第一个if分支的load方法

继续跟进

  1. private int load(Class<?> source) {
  2. // 省略
  3. if (isComponent(source)) {
  4. this.annotatedReader.register(source);
  5. return 1;
  6. }
  7. return 0;
  8. }

该方法先通过isComponent方法判断了主类是不是被@Component注解,如果是,那么调用注解方式的阅读器,注册该资源。

跟进isComponent方法,看看怎么判断的

  1. private boolean isComponent(Class<?> type) {
  2. // 找到是否匹配@Component注解
  3. if (AnnotationUtils.findAnnotation(type, Component.class) != null) {
  4. return true;
  5. }
  6. // 省略
  7. }

其实就是找这个类是否有@Component注解,但请注意我们通常都使用@SpringBootApplication这个注解,并没有直接注解@Component。而@SpringBootApplication是一个组合注解,其中就组合了@Component

而AnnotationUtils.findAnnotation方法将会递归遍历注解,最终找到@Component。

isComponent判断为true以后,我们再跟进annotationReader.register(source)阅读一下读取主类的过程

  1. public void register(Class<?>... annotatedClasses) {
  2. for (Class<?> annotatedClass : annotatedClasses) {
  3. registerBean(annotatedClass);
  4. }
  5. }

继续跟进registerBean方法

  1. public void registerBean(Class<?> annotatedClass) {
  2. doRegisterBean(annotatedClass, null, null, null);
  3. }

再跟进doRegisterBean方法,该方法比较长,我们省略掉一些次要的部分

  1. <T> void doRegisterBean(
  2. Class<T> annotatedClass,
  3. @Nullable Supplier<T> instanceSupplier,
  4. @Nullable String name,
  5. @Nullable Class<? extends Annotation>[] qualifiers,
  6. BeanDefinitionCustomizer... definitionCustomizers
  7. ) {
  8. // 先包装成BeanDefinition
  9. AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
  10.  
  11. // 解析scope元数据
  12. ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
  13. abd.setScope(scopeMetadata.getScopeName());
  14.  
  15. // 生成bean的名
  16. String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
  17.  
  18. // 解析一些常见的注解元数据
  19. AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
  20.  
  21. BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
  22. definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
  23. // 注册BeanDefinition到ApplicationContext
  24. BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
  25. }

可以看到,doRegisterBean方法的主要逻辑就是包装并解析出一个BeanDefinition,然后调用registerBeanDefinition方法把BeanDefinition给注册到ApplicationContext中。

注册相关的本文就不再继续展开了,后续的文章会跟进这些内容。

总结

总的来说,prepareContext方法主要就是为了加载并注册主类的BeanDefinition到ApplicationContext。这里注意!我们一直都在说注册到ApplicationContext,但熟悉spring的都会知道无论是Bean还是BeanDefinition都是注册到BeanFactory中的。但我们一直没有严格区分它,后续的文章我们将会把ApplicationContext和BeanFactory进行区分。

springboot启动流程(六)ioc容器刷新前prepareContext的更多相关文章

  1. springboot启动流程(目录)

    springboot出现有段时间了,不过却一直没有怎么去更多地了解它.一方面是工作的原因,另一方面是原来觉得是否有这个必要,但要持续做java似乎最终逃不开要去了解它的命运.于是考虑花一段时间去学习一 ...

  2. SpringBoot启动流程分析(六):IoC容器依赖注入

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

  3. SpringBoot启动流程分析(四):IoC容器的初始化过程

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

  4. springboot启动流程(七)ioc容器refresh过程(上篇)

    所有文章 https://www.cnblogs.com/lay2017/p/11478237.html 正文 在前面的几篇文章中,我们看到Environment创建.application配置文件的 ...

  5. SpringBoot启动流程分析(二):SpringApplication的run方法

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

  6. SpringBoot启动流程分析(三):SpringApplication的run方法之prepareContext()方法

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

  7. SpringBoot启动流程分析(五):SpringBoot自动装配原理实现

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

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

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

  9. SpringBoot启动流程及其原理

    Spring Boot.Spring MVC 和 Spring 有什么区别? 分别描述各自的特征: Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等:但他们的 ...

随机推荐

  1. hwclock设置时间的调用过程是怎样的?

    调用过程如下: hwclock -w -> xioctl(RTC_SET_TIME); -> rtc_dev_ioctl() -> rtc_set_time()

  2. SurfaceView动态背景效果实现

    package com.loaderman.customviewdemo; import android.content.Context; import android.graphics.*; imp ...

  3. Map接口和Collection接口的区别

    * Map是双列的,Collection是单列的 * Map的键唯一,Collection的子体系Set是唯一的 * Map集合的数据结构值针对键有效,跟值无关;Collection集合的数据结构是针 ...

  4. 导入GoogleClusterData到MySQL

    本篇随笔记录如何导入google-cluster-data-2011-1-2的 job_events和task_events到MySQL 1. 下载数据 download_job_events: im ...

  5. ISO/IEC 9899:2011 条款5——5.2.1 字符集

    5.2.1 字符集 1.两个字符集和它们相关联的依次顺序应该被定义:写在源文件中的集合(源字符集),以及在执行环境中被解释的集合(执行字符集).每个集合此外被划分为一个基本字符集,其内容由本子条款给出 ...

  6. python基础之线程、进程、协程

    线程 线程基础知识 一个应用程序,可以多进程.也可以多线程. 一个python脚本,默认是单进程,单线程的. I/O操作(音频.视频.显卡操作),不占用CPU,所以: 对于I/O密集型操作,不会占用C ...

  7. python 运算符和小数据池

    计算机可以进行的运算有很多种,可不只加减乘除这么简单,运算按种类可分为算数运算.比较运算.逻辑运算.赋值运算.成员运算.身份运算.位运算,今天我们暂只学习算数运算.比较运算.逻辑运算.赋值运算 算数运 ...

  8. git 提交大小超过100M

    #MsnDialog.ad, #MyMoveAd, #QQ_Full, #ad-SNSSplashAd, #ad6cn, #adBody07, #adLeftFloat, #adRightFloat, ...

  9. F2812 DSP程序运行在片内RAM和FLASH的区别

    F2812 DSP程序运行在片内RAM和片内FLASH的区别 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 说明:F2812是带有内部Flash的DSP,与 ...

  10. python map函数(23)

    截至到目前为止,其实我们已经接触了不少的python内置函数,而map函数也是其中之一,map函数是根据指定函数对指定序列做映射,在开发中使用map函数也是有效提高程序运行效率的办法之一. 一.语法定 ...