前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试官:说说SpringBoot中Spring容器的启动过程

面试官:说说SpringBoot中Spring容器的启动过程

作者头像
业余草
发布2020-10-10 11:26:01
2.6K0
发布2020-10-10 11:26:01
举报
文章被收录于专栏:业余草业余草

你知道的越多,不知道的就越多,业余的像一棵小草!

你来,我们一起精进!你不来,我和你的竞争对手一起精进!

编辑:业余草

来源:r6f.cn/b47K

推荐:https://www.xttblog.com/?p=5107

一. 前言

Spring家族特别庞大,对于开发人员而言,要想全面征服Spring家族,得花费不少的力气。俗话说,打蛇打七寸,那么Spring家族的“七寸”是什么呢?我心目中的答案一直都是 Spring Framework!

本篇文章记录我自己在学习Spring Framework的过程中的一小部分源码解读和梳理,来谈一谈Spring 容器在启动过程中是如何扫描Bean的。

二. 学习方法论

我相信每个想变成优秀的开发人员都想弄懂Spring源码,我亦如此。于是通过很多途径来找Spring源码的学习资料、买书、看视频等等。到头来发现只有自己静下心来一步一步跟着源码调试,一行一行的深入理解,才能深入理解Spring的奥妙!这个过程很枯燥,但优秀的猎手最能耐得住寂寞和浮躁!

我们知道,Spring容器的启动方式有多种:XML文件、注解、Java Config。在实际的使用中并不是选择其中某一种,而是相互搭配。其底层的容器启动过程是一样的,只是入口变了而已。另外,学习Spring的最佳方式就是自己将源码工程构建出来,这样便于源码阅读、备注、修改。构建出来的工程长这样:

三. 代码入口

话不多说直接开干!代码入口如下:

代码语言:javascript
复制
@Configuration
@ComponentScan("com.leon.funddatahouse")
public class Config {
}

public class MyApplication {
    public static void main(String[] args) {
        // 我们基于注解的方式
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Config.class);
        // 如果基于XML文件配置,则也可以如下:
        // ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
    }
}

在构造方法中,总共做了3件事情。这三件事情包括了整个Spring容器启动的所有过程!啃碎他们,便成功了一半!

代码语言:javascript
复制
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
        // 1.调用默认构造方法,进行容器环境的准备
        this();
        // 2.基于配置类注册相关信息
        register(componentClasses);
        // 3.刷新整个容器
        refresh();
    }

四. 解析之前

在解析之前,先将容器和BeanFactory的UML类图放出。原因在于它们担任的角色、具备的功能太多太强大了,同时这也增加了源码理解的难度。因此这里先放出UML类图作为手册查看,便于理解源码。

4.1 容器UML类图

4.2 BeanFactoryUML类图

五. 源码解析

5.1 构造方法解析

5.1.1 初始化容器中的BeanFactory

在构造方法中,显式的调用了this(),既无参构造方法:

代码语言:javascript
复制
public AnnotationConfigApplicationContext() {
    // 1.实例化容器中的reader. 后面会详细解析
    this.reader = new AnnotatedBeanDefinitionReader(this);
    // 2.实例化容器中的scanner.后面会详细解析
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}

乍看一眼,这个无参构造方法做了两件事情,其实不然。它实际上等同于:

代码语言:javascript
复制
public AnnotationConfigApplicationContext() {
    // 1.调用父类构造方法
    super();
    // 2.实例化容器中的reader. 后面会详细解析
    this.reader = new AnnotatedBeanDefinitionReader(this);
    // 3.实例化容器中的scanner.后面会详细解析
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}

这一点很关键, 如果没有意识到这里隐形调用了父类构造方法的话, 那么接下来的路没法走, 因为在父类构造器中做了一件大事情:

代码语言:javascript
复制
// 在父类的构造方法中, 创建了容器中的BeanFactory.至此,容器中有了第一个程序创建的属性:beanFactory
public GenericApplicationContext() {
    // 初始化容器的beanFactory,类型为DefaultListableBeanFactory
    this.beanFactory = new DefaultListableBeanFactory();
}

注意,面试官提问:BeanFactory 和 FacotryBean的区别

5.1.2 实例化容器中的Reader

reader最主要的目的是用于辅助注册BeanDefinition,其具体的使用后文在介绍,这里我们只需知道它包含了哪些东西。

代码语言:javascript
复制
// 入参registry就是容器本身。因为通过上面的UML类图可以发现,容器间接继承了BeanDefinitionRegistry
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
    // getOrCreateEnvironment() 方法最主要是获取环境。实际类型其实默认的就是StandardEnvironment类。这里的环境包括两方面:
    // 1.systemEnvironment:操作系统环境。这样,Spring就可以获取到操作系统、CPU核心数等操作系统本身的数据。
    // 2.systemProperties:JVM的环境变量。这样,Spring就可以获取到JVM的基础数据,比如我们在启动参数中手动设置的环境变量等。
    this(registry, getOrCreateEnvironment(registry));
}

这里通过this() 调用了reader内部另一个构造方法:

代码语言:javascript
复制
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    Assert.notNull(environment, "Environment must not be null");
    // 设置registry,已经知道它的就是容器本身:AnnotationConfigApplicationContext
    this.registry = registry;
    // 创建条件处理器
    this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
    // 非常关键!提前往容器中注册一些必要的后置处理器
    AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

这个构造方法很重要, 因为它涉及到spring容器当中的两个重要成员:条件解析器和后置处理器!

5.1.2.1 实例化条件处理器

相信熟悉Spring的人一定都知道或用过@ConditionalOnBean / @ConditionalOnClass 等条件注解.而这些条件注解的解析就是ConditionEvaluator.

代码语言:javascript
复制
public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry,
        @Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {
    // 实际上是委托给内部类ConditionContextImpl
    this.context = new ConditionContextImpl(registry, environment, resourceLoader);
}

// ------------分割线------------------

// 内部的ConditionContextImpl构造器
public ConditionContextImpl(@Nullable BeanDefinitionRegistry registry,
        @Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {
    // 再说一遍,registry的实际类型就是 AnnotationConfigApplicationCont
    this.registry = registry;
    // 获取beanFactory,我们也知道了beanFactory其实就是 ConfigurableListableBeanFactory
    this.beanFactory = deduceBeanFactory(registry);
    // 从容器中获取environment,前面介绍过,容器中的environment的封装类是 StandardEnvironment
    this.environment = (environment != null ? environment : deduceEnvironment(registry));
    // 资源加载器. 通过UML类图可以发现,resourceLoader就是容器, 因为容器间接继承了ResourceLoader
    this.resourceLoader = (resourceLoader != null ? resourceLoader : deduceResourceLoader(registry));
    // 类加载器. 实际上就是获取beanFactory的类加载器。理应如此,容器当中的类加载器肯定要一致才行
    this.classLoader = deduceClassLoader(resourceLoader, this.beanFactory);
}

后面在解析BeanDefinition时我们还会遇到ConditionEvaluator, 其具体源码解析我后面会用专门的文章来解析,本篇文章我们只需要知道它的作用即可。

5.1.2.2 注册一部分后置处理器

ConditionEvaluator初始化完成之后,接下来就特别重要了,因为在这里将提前注入一些后置处理器:

代码语言:javascript
复制
public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
    // 空壳方法,实际委托给重载的方法
    registerAnnotationConfigProcessors(registry, null);
}

重载的方法如下(高能预警):

代码语言:javascript
复制
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
        BeanDefinitionRegistry registry, @Nullable Object source) {
    // 获取容器中的beanFactory,通过前面的解析,我们知道,这里一定会获取到。因此将进入if分支
    DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
    if (beanFactory != null) {
        // 此时beanFactory的属性dependencyComparator为null,因为初始化过程中,内部成员变量如果没有默认值,则默认为null,
        // 所以如果第一次进来, 这里的判断一定成立,对dependencyComparator进行设置。
        // AnnotationAwareOrderComparator继承了OrderComparator,
        // 因此可以对实现了Ordered接口、打上@Order或者@Priority注解的类进行排序。
        // 也就是说,在这里设置beanFactory中的orderComparator,以支持解析bean的排序功能。
        if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
            beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
        }
        // beanFactory初始化时,默认为SimpleAutowireCandidateResolver,因此第一次进来时这里的判断也一定成立。
        // ContextAnnotationAutowireCandidateResolver最主要的作用就是支持@Lazy注解的类的处理。
        if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
            beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
        }
    }

    // 初始化一个bdh容器,用于盛放接下来将解析出来的后置处理器的bd。
    Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

    // 容器在第一次初始化时,内部一个bd都没有的。
    // 也就是说,从这里开始,容器将第一次装载bd,而这里的这些bd都是spring自带的后置处理器。

    // 获取并注册ConfigurationClassPostProcessor后置处理器 的bd
    if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    // 获取并注册AutowiredAnnotationBeanPostProcessor后置处理器 的bd
    if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    // 获取并注册CommonAnnotationBeanPostProcessor后置处理器 的bd
    if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    // 获取并注册PersistenceAnnotationBeanPostProcessor后置处理器 的bd
    if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition();
        try {
            def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
                    AnnotationConfigUtils.class.getClassLoader()));
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
        }
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    // 获取并注册EventListenerMethodProcessor后置处理器 的bd
    if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
    }

    // 获取并注册DefaultEventListenerFactory后置处理器 的bd
    if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
    }

    return beanDefs;
}

这个方法首次出现了BeanDefinition这个类. Spring的BeanDefinition相当于Java的Class

通过该方法之后, beanFactory中就存在了以上6个bd:

曾经有人跟我说, 掌握了Spring的后置处理器, 那么整个Spring就掌握了10%! 可见其重要性。但是在这里先不展开后置处理器(太多了),本篇文章的主线是容器启动过程。

5.1.2.3 reader初始化过程小结

到这里reader部分的初始化终于完成了。总结一下,reader的初始化主要干了这些事情:1.创建并设置容器当中的Environment属性。即默认为StandardEnvironment类。2.创建并设置容器当中的条件解析器,即ConditionEvaluator,其内部实际委托给内部类ConditionContextImpl。3.注册6个后置处理器到容器当中。注意这里仅是生成了后置处理器的BeanDefinition。还并没有进行bean解析和后置处理的执行。

5.1.3 实例化容器中的Scanner

解析完reader之后,继续解析scanner。这里的scanner的实际类型是ClassPathBeanDefinitionScanner。它最主要的目的就是扫描类路径下所有的class文件能否解析为bd。其最终调用的构造方法如下:

代码语言:javascript
复制
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
    // 1.委托给内部的另一个构造方法
    this(registry, true);
}
// ------------------------分割线-------------------------
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
    // 2.又委托给内部的另一个构造方法 >_<
    // 从上面的入参可以知道 入参的registry实际就是容器本身, 并使用默认的filter.这个filter干什么的,下面会解析
    this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
}
// ------------------------分割线-------------------------
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
        Environment environment) {
    // 3.又委托给内部的另一个构造方法 T^T
    this(registry, useDefaultFilters, environment,
            (registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
}
// ------------------------分割线-------------------------
// 4. 终于见到了庐山真面目(不容易) ^_^
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
        Environment environment, @Nullable ResourceLoader resourceLoader) {

    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    // 再说一遍, registry就是容器!
    this.registry = registry;
    // 重要!!! 是否包括默认过滤器。从上面的入参可以知道, 这里的useDefaultFilters = true,因此会进入if分支
    if (useDefaultFilters) {
        registerDefaultFilters();
    }
    // 设置环境变量
    setEnvironment(environment);
    // 设置资源加载器
    setResourceLoader(resourceLoader);
}
5.1.3.1 registerDefaultFilters()方法

从最终的构造方法我们知道, Scanner在扫描的过程中,会使用过滤策略,并且使用了默认的过滤策略.默认策略就是以下这个方法解析.

代码语言:javascript
复制
protected void registerDefaultFilters() {
    // 扫描@Component注解的类
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));
    ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
    try {
        // 扫描所有@ManageBean的类
        this.includeFilters.add(new AnnotationTypeFilter(
                ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
        logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
    }
    try {
        // 扫描所有@Named的类
        this.includeFilters.add(new AnnotationTypeFilter(
                ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
        logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
    }
}

这里的一个知识点:@ManageBean和@Named的作用和@Component是一样的。只是我们通常习惯使用@Component。

为什么这里没有添加默认扫描@Service、@Repository、@Controller呢?原因很简单,这些注解都间接继承了@Component了。到这里,scanner解析完毕,它做的最主要的事情就是添加默认的过滤器策略以便在后续中可以扫描出@Component注解的类。

六 默认构造方法小结

现在我们再来看一下构造方法:

代码语言:javascript
复制
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    // 1.调用默认构造方法,进行容器环境的准备
    this();
    register(componentClasses);
    refresh();
}

从入口看, 就只有这三行代码, 但其中的第一行,调用默认构造方法就做了这么多准备工作,其中也牵扯出了一些Spring整个体系中最重要的几个组件,比如BeanFactory / BeanDefinition / BeanDefinitionReader / BeanDefinitionScanner / Environment / ConditionEveluator / PostProcessor等等.随便拿一个出来都够喝一壶! 。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-10-03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 你知道的越多,不知道的就越多,业余的像一棵小草!
  • 编辑:业余草
  • 来源:r6f.cn/b47K
  • 推荐:https://www.xttblog.com/?p=5107
  • 一. 前言
  • 二. 学习方法论
  • 三. 代码入口
  • 四. 解析之前
    • 4.1 容器UML类图
      • 4.2 BeanFactoryUML类图
      • 五. 源码解析
        • 5.1 构造方法解析
          • 5.1.1 初始化容器中的BeanFactory
          • 5.1.2 实例化容器中的Reader
          • 5.1.3 实例化容器中的Scanner
      • 六 默认构造方法小结
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档