专栏首页Jimoer你来说一下springboot启动时的一个自动装配过程吧

你来说一下springboot启动时的一个自动装配过程吧

前言

继续总结吧,没有面试就继续夯实自己的基础,前阵子的在面试过程中遇到的各种问题陆陆续续都会总结出来分享给大家,这次要说的也是面试中被问到的一个高频的问题,我当时其实没答好,因为很早之前是看过springboot启动过程的源码,但是时间隔得有点久了(两年多没用过springboot),所以当时也没答好。这次好好总结这部分知识。

SpringApplication.run()

我看网上好多介绍springboot自动装配过的文章时,上来就直接说@SpringBootApplication注解是一个复合注解,从这个注解开始介绍springboot是如何将配置项进行加载的。其实我觉得难道不应该是先启动了spring的容器,然后才能扫到注解,然后才能解析注解吗?也可能是大家觉得创建容器刷新容器这些基础操作都默认知道的,所以就都没说。 但我在分析springboot自动装配的时候,要先从SpringApplication.run()方法开始。

我们进入到SpringApplication这个类中看一下run()方法的核心实现,差不多每一行我都加上了注释了。

SpringApplication.run()方法中,我把关键点用序号标识出来了。

  1. 第一个就是创建ApplicationContext容器。
  2. 第二个是刷新ApplicationContext容器。

在创建ApplicationContext时,会根据用户是否明确设置了ApplicationContextClass类型以及初始化阶段的推断结果,决定为当前SpringBoot应用创建什么类型的ApplicationContext。

创建完成ApplicationContext容器后,我们接着回到SpringApplication.run()方法中。 下面开始初始化各种插件在异常失败后给出的提示。 然后执行准备刷新上下文的一些操作。其实prepareContext()方法也是非常关键的,它起到了一个承上启下的作用。下面我们来看一下prepareContext()方法里面具体执行了什么。

关键的地方我也标注出来了,主要就是getAllSoures()方法,这个方法中,获取到的一个source就是启动类DemoApplication。

这样就通过获取这个启动类就可以在后load()方法中取加载这个启动类到容器中。

然后,后面再通过listeners.contextLoaded(context); 将所有监听器加载到ApplicationContext容器中。

最后就是我们上面说的核心的第二部刷新ApplicationContext容器操作,如果没有这一步操作上面的内容也都白做的,通过SpringApplication的refreshContext(context)方法完成最后一道工序将启动类上的注解配置,刷新到当前运行的容器环境中。

启动类上的注解

上面我们说到在SpringApplication的run()方法中,通过调用自己的prepareContext()方法,在prepareContext()方法中又调用getAllSources()方法,然后去获取启动类,然后通过SpringApplication的load()方法,去加载启动类,然后在刷新容器的时候就会去将启动类在容器中进行实例化。

在刷新ApplicationContext容器时,就开始解析启动类上的注解了。

启动类DemoApplication就只有一个注解@SpringBootApplication,那么下面来看一下这个注解:

可以看到这个注解是一个复合注解,有三个关键注解需要说明一下。

@SpringBootConfiguration

@SpringBootConfiguration这个注解说明再点进去查看详情发现就是一个@Configuration注解,这说明启动类就是一个配置类。支持Spring以JavaConfig的形式启动。

@ComponentScan

这个注解,从字面的意思上也能看出来,就是组件扫描的意思,即默认扫描当前package以及其子包下面的spring的注解,例如:@Controller@Service@Component等等注解。

@EnableAutoConfiguration

@EnableAutoConfiguration这个注解也是一个复合注解:

这个注解是比较核心的一个注解,springboot的主要自动配置原理基本上都来自@EnableAutoConfiguration这个注解的配置,那么我们通过看这个注解的源码可以发现有两个注解比较重要的。

  • 一个是@AutoConfigurationPackage,自动配置包。
  • 另一个是@Import(AutoConfigurationImportSelector.class),自动引入组件。

@AutoConfigurationPackage这个注解字面的意思是自动配置包,那么我们点进去看看里面是什么样的。

还是一个复合注解,但是最终依赖的确实@Import这个注解,这个注解后面我们会介绍,现在先明白它就是给Spring容器引入组件的功能的一个注解。 那么我们接着来看看AutoConfigurationPackages.Registrar.class这个类里面的代码。

这两张图就是这个AutoConfigurationPackages.Registrar这个类的关键部分,说实话,我是没看出来什么东西。但是网上搜到的是这个register()方法的作用是,用来自动注册一些组件中的配置,例如JPA的@Entity这个注解,这里就是会开启自动扫描这类注解的功能。

@Import(AutoConfigurationImportSelector.class)

我们接着回来看@EnableAutoConfiguration下的@Import(AutoConfigurationImportSelector.class)这个注解的功能。进入到AutoConfigurationImportSelector这个类里面后源码如下:

然后我们进入getAutoConfigurationEntry()方法来看看:

我们继续进入getCandidateConfigurations()方法:

看来最核心的方法是SpringFactroiesLoader.loadFactoryNames()方法了,我们再进入看看:

包的好深,居然还有一层,那么继续进入loadSpringFactories()方法。

终于到最后一层了,算是“拨开云雾见天日,守得云开见月明”,下面就来梳理一下loadSpringFactories()方法。 首先FACTORIES_RESOURCE_LOCATION这个常量的值是: "META-INF/spring.factories"

/**
 * The location to look for factories.
 * <p>Can be present in multiple JAR files.
 */
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

所以第一个端核心代码的意思是: 启动的时候会扫描所有jar包下META-INF/spring.factories这个文件。第二段代码的意思是将这些扫描到的文件转成Properties对象,后面两个核心代码的意思就是说将加载到的Properties对象放入到缓存中。

然后getCandidateConfigurations()方法,是只获取了key是EnableAutoConfiguration.class的配置。

我们看到getCandidateConfigurations()方法,通过SpringFactoriesLoader.loadFactoryNames()获取到了118个配置。

那么我们来看一个spring.factories文件中的内容是什么样子的呢?

原来是这种形式的,看来这和上一篇文章中讲解的Java中的SPI机制加载接口实现很像啊,其实通过查阅资料发现,这就是一种自定义SPI的实现方式的功能。 那么我们以第一个配置类: org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration来看一下,这些类都是如果实现的。 打开org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration的源码:

我们看到这个类有三个注解@Configuration@AutoConfigureAfter@ConditionalOnProperty、因为有@Configuration注解所以它也是一个配置类,然后第二注解中的参数类JmxAutoConfiguration.class进入之后是这样的:

也是存在@ConditionalOnProperty注解的。那看来关键点就是@ConditionalOnProperty这个注解了。 这个注解其实是一个条件判断注解,这个条件注解后面的参数的意思是当存在系统属性前缀为spring.application.admin,并且属性名称为enabled,并且值为true时,才加载当前这个Bean并进行实例化。

这种spring4.0后面出现的的条件注解,可以极大的增加了框架的灵活性和扩展性,可以保证很多组件可以通过后期配置,而且阅读源码的人,通过这些注解就能明白在什么情况下才会实例化当前Bean。

后面还有不少这种条件注解呢:

@ConditionalOnBean:当容器里有指定Bean的条件下 @ConditionalOnClass:当类路径下有指定的类的条件下 @ConditionalOnExpression:基于SpEL表达式为true的时候作为判断条件才去实例化 @ConditionalOnJava:基于JVM版本作为判断条件 @ConditionalOnJndi:在JNDI存在的条件下查找指定的位置 @ConditionalOnMissingBean:当容器里没有指定Bean的情况下 @ConditionalOnMissingClass:当容器里没有指定类的情况下 @ConditionalOnWebApplication:当前项目时Web项目的条件下 @ConditionalOnNotWebApplication:当前项目不是Web项目的条件下 @ConditionalOnProperty:指定的属性是否有指定的值 @ConditionalOnResource:类路径是否有指定的值 @ConditionalOnOnSingleCandidate:当指定Bean在容器中只有一个,或者有多个但是指定首选的Bean

这些注解其实都是通过@Conditional注解扩展而来的,只是使用了不同的组合条件来判断是否需要加载和初始化当前Bean。

总结

好了,最后总结一下,当面试官问springboot的自动装配原理的时候,不能这么长篇大论的说吧,毕竟这么多内容也记不住啊。 所以总结: springboot启动时,是依靠启动类的main方法来进行启动的,而main方法中执行的是SpringApplication.run()方法,而SpringApplication.run()方法中会创建spring的容器,并且刷新容器。而在刷新容器的时候就会去解析启动类,然后就会去解析启动类上的@SpringBootApplication注解,而这个注解是个复合注解,这个注解中有一个@EnableAutoConfiguration注解,这个注解就是开启自动配置,这个注解中又有@Import注解引入了一个AutoConfigurationImportSelector这个类,这个类会进过一些核心方法,然后去扫描我们所有jar包下的META-INF下的spring.factories文件,而从这个配置文件中取找key为EnableAutoConfiguration类的全路径的值下面的所有配置都加载,这些配置里面都是有条件注解的,然后这些条件注解会根据你当前的项目依赖的jar包以及是否配置了符合这些条件注解的配置来进行装载的。

这就是springboot自动配置的过程。

其实上面这些内容还是有点多,而且还有好多注解的单词也不好记,那换成大白话,再精炼一下:

SpringBoot在启动的时候会调用run()方法,run()方法会刷新容器,刷新容器的时候,会扫描classpath下面的的包中META-INF/spring.factories文件,在这个文件中记录了好多的自动配置类,在刷新容器的时候会将这些自动配置类加载到容器中,然后在根据这些配置类中的条件注解,来判断是否将这些配置类在容器中进行实例化,这些条件主要是判断项目是否有相关jar包或是否引入了相关的bean。这样springboot就帮助我们完成了自动装配。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 手把手教你实现自定义Spring Boot的 Starter

    上篇文章《天天用SpringBoot,它的自动装配原理却说不出来》我们有说springBoot的自动装配怎么实现的(建议最好先看下篇文章,因为前后有关系),这篇...

    java金融
  • 保姆级教程,手把手教你实现一个SpringBoot的starter

    上篇文章《天天用SpringBoot,它的自动装配原理却说不出来》我们有说springBoot的自动装配怎么实现的(建议最好先看下篇文章,因为前后有关系),这篇...

    java金融
  • 阿里面试:看你springBoot用的比较溜来,说说springboot自动装配是怎么回事?

    最近有个读者在面试,面试中被问到了这样一个问题“看你项目中用到了springboot,你说下springboot的自动配置是怎么实现的?”这应该是一个sprin...

    java金融
  • 面试高频题:springBoot自动装配的原理你能说出来吗?

    最近有个读者在面试,面试中被问到了这样一个问题“看你项目中用到了springboot,你说下springboot的自动配置是怎么实现的?”这应该是一个sprin...

    java金融
  • 『高级篇』docker容器来说微服务导学(一)

    PS:下面我们一步一步spring cloud+spring boot创建的微服务,部署在服务编排框架上。

    IT架构圈
  • 『高级篇』docker之springboot,springcloud(八)

    PS:下面我们一步一步spring cloud+spring boot创建的微服务,部署在服务编排框架上。

    IT架构圈
  • Spring Cloud Cli 初体验

    SpringBoot CLI 是spring Boot项目的脚手架工具。而本文的Spring Cloud cli则是基于SpringBoot Client的一个...

    pollyduan
  • SpringBoot这只怪物到底是如何跑起来的?

    不得不说 SpringBoot 太复杂了,我本来只想研究一下 SpringBoot 最简单的 HelloWorld 程序是如何从 main 方法一步一步跑起来的...

    Java团长
  • SpringBoot自动配置原理,这篇文章讲透了!

    原文:juejin.im/post/5ce5effb6fb9a07f0b039a14

    Bug开发工程师
  • 这样讲 SpringBoot 自动配置原理,你应该能明白了吧

    小伙伴们是否想起曾经被 SSM 整合支配的恐惧?相信很多小伙伴都是有过这样的经历的,一大堆配置问题,各种排除扫描,导入一个新的依赖又得添加新的配置。自从有了 S...

    江南一点雨
  • 快速搭建 SpringCloud 微服务开发环境的脚手架

    HelloGitHub 推出的《讲解开源项目》[1]系列,今天给大家带来一款基于 SpringCloud2.1 的微服务开发脚手开源项目——SpringClou...

    HelloGitHub
  • SpringBoot Kafka 整合使用

    前提 假设你了解过 SpringBoot 和 Kafka。 1、SpringBoot 如果对 SpringBoot 不了解的话,建议去看看 DD 大佬 和 纯洁...

    zhisheng
  • Spring Boot自动配置原理,你必须懂

    小伙伴们是否想起曾经被 SSM 整合支配的恐惧?相信很多小伙伴都是有过这样的经历的,一大堆配置问题,各种排除扫描,导入一个新的依赖又得添加新的配置。自从有了 S...

    南风
  • SpringBoot四大核心之自动装配——源码解析

    我们点进去源码可以发现,@SpringBootApplication是一个组合注解,其中上面那三个是属于Java提供的元注解,@Inherited是指可继承的(...

    用户5546570
  • 导图梳理springboot手动、自动装配,让springboot不再难懂

    在学springboot之前,你必须有spring、spring mvc基础,springboot的诞生其实就是用来简化新Spring应用的初始搭建以及开发过程...

    java思维导图
  • 导图梳理springboot手动、自动装配,让springboot不再难懂

    在学springboot之前,你必须有spring、spring mvc基础,springboot的诞生其实就是用来简化新Spring应用的初始搭建以及开发过程...

    JAVA葵花宝典
  • (一) SpringBoot起飞之路-HelloWorld

    开发一个 Web 项目,从入门的 JavaWeb,也就是 Servlet + Tomcat 的那一套,学完了这部分,大部分人就会去接触一些框架,像SSM、SSH...

    BWH_Steven
  • SpringBoot基础(一、快速入门)

    当你开始检索SpringBoot开始,应该有一个类似的想法,跟Spring有什么关系,SpringBoot翻译成中文,Spring引导。我们也自然而然的想起Sp...

    营琪
  • springboot 入门教程(3)-运行原理、关键注解和配置

    springboot 入门教程(3)-运行原理、关键注解和配置 摘要: 相信大家接触过springboot以后都觉得使用他搭建一个web很简单,但是为什么他就能...

    Java帮帮

扫码关注云+社区

领取腾讯云代金券