你来说一下springboot的启动时的一个自动装配过程吧
前言
继续总结吧,没有面试就继续夯实自己的基础,前阵子的在面试过程中遇到的各种问题陆陆续续都会总结出来分享给大家,这次要说的也是面试中被问到的一个高频的问题,我当时其实没答好,因为很早之前是看到springboot的启动的一个过程的源码的,但是时间隔得有点久了(两年多没用过springboot),所以当时也没答好。这次好好总结这部分知识。
SpringApplication.run()
我看网上好多介绍springboot自动装配过的文章时,上来就直接说@SpringBootApplication
注解是一个复合注解,从这个注解开始介绍springboot是如何将配置项进行加载的。其实我觉得难道不应该是先启动了spring的容器,然后才能扫到注解,然后才能解析注解吗?也可能是大家觉得创建容器刷新容器这些基础操作都默认知道的,所以就都没说。
但我在分析springboot自动装配的时候,要先从SpringApplication.run()
方法开始。
我们进入到SpringApplication
这个类中看一下run()
方法的核心实现,差不多每一行我都加上了注释了。
SpringApplication.run()
方法中,我把关键点用序号标识出来了。
- 第一个就是创建ApplicationContext容器。
- 第二个是刷新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就帮助我们完成了自动装配。
你来说一下springboot的启动时的一个自动装配过程吧的更多相关文章
- SpringBoot项目启动时链接数据库很慢
SpringBoot项目启动时链接数据库很慢 springboot项目在启动时候,如下图所示,链接数据库很慢 解决方法:在mysql 的配置文件中 配置 skip-name-resolve
- SpringBoot在启动时的多环境配置以及加载顺序
通常我们在开发完成一个SpringBoot项目时,总是要打包部署的. 在启动SpringBoot应用时,我们常常会使用命令java -jar xxx.jar来启动这个服务. 命令java -jar 除 ...
- springBoot启动时让方法自动执行的几种实现方式
一.开篇名义 在springBoot中我们有时候需要让项目在启动时提前加载相应的数据或者执行某个方法,那么实现提前加载的方式有哪些呢?接下来我带领大家逐个解答 1.实现ServletContextAw ...
- SpringBoot程序启动时执行初始化代码
因项目集成了Redis缓存部分数据,需要在程序启动时将数据加载到Redis中,即初始化数据到Redis. 在SpringBoot项目下,即在容器初始化完毕后执行我们自己的初始化代码. 第一步:创建实现 ...
- SpringBoot程序启动时在Oracle数据库中建表充值
例子工程下载链接:https://files.cnblogs.com/files/xiandedanteng/gatling20200428-1.zip 需求:在工程启动时在Oracle数据库中建表. ...
- springBoot和MyBatis整合中出现SpringBoot无法启动时处理方式
在springBoot和Myatis 整合中出现springBoot无法启动 并且报以下错误 Description: Field userMapper in cn.lijun.control ...
- Oracle 12c启动时PDBs的自动打开
Pluggable Database(PDB)为Oracle 12c中的一个重要的新特性, 但启动12c实例时并不会自动打开PDB数据库,这样,在启动实例后必须手动打开PDBs. 1. 实例启动后,手 ...
- SpringBoot:认认真真梳理一遍自动装配原理
前言 Spring翻译为中文是“春天”,的确,在某段时间内,它给Java开发人员带来过春天,但是随着我们项目规模的扩大,Spring需要配置的地方就越来越多,夸张点说,“配置两小时,Coding五分钟 ...
- 天天用SpringBoot居然还不知道它的自动装配的原理?
引言 最近有个读者在面试,面试中被问到了这样一个问题"看你项目中用到了springboot,你说下springboot的自动配置是怎么实现的?"这应该是一个springboot里面 ...
随机推荐
- 无所不能的Embedding4 - Doc2vec第二弹[skip-thought & tf-Seq2Seq源码解析]
前一章Doc2Vec里提到,其实Doc2Vec只是通过加入Doc_id捕捉了文本的主题信息,并没有真正考虑语序以及上下文语义,n-gram只能在局部解决这一问题,那么还有别的解决方案么?依旧是通用文本 ...
- Spring第二天,你必须知道容器注册组件的几种方式!学废它吊打面试官!
前一篇<不要再说不会Spring了!Spring第一天,学会进大厂!>文章可点击下方链接进行回看. 不要再说不会Spring了!Spring第一天,学会进大厂! 今天将继续讲解Spri ...
- Java之定时任务全家桶
定时任务应用非常广泛,Java提供的现有解决方案有很多.本次主要讲schedule.quartz.xxl-job.shedlock等相关的代码实践. 一.SpringBoot使用Schedule 核心 ...
- 微信公众号获取openid(php实例)
微信公众号获取openid 公众号获取openid的方法跟小程序获取openid其实是一样的,只是code获取的方式不一样 小程序获取code: 用户授权登录时调用wx.login即可获取到code ...
- CorelDRAW“出血线”的精准预设与辅助线便捷操作
CorelDRAW软件是一款常用的制图工具,非常适合用于印刷品输出,各种印刷图文制作都依赖于它.所以,我们设计者每次用CorelDRAW制图的一个关键就是要做好"标尺辅助线"设置, ...
- 在FL Studio中通过Key Tracking来改善声音
FL Studio中的关键点跟踪(Key Tracking),是一种为MIDI添加更多动态效果的便利工具,在FL Studio中通过使用这个插件能力,我们无需担心自动化或手动调整参数等比较麻烦的问题. ...
- jQuery 第十章 工具方法-高级方法 $.ajax() $.Callbacks() .....
$.ajax() $.Callbacks() $.Deferred() .then() $.when() ---------------------------------------------- ...
- java运算符与程序逻辑控制
一.运算符 java中的运算符大致分为四种:数学运算符,关系运算符.逻辑运算符.位运算 1.数学运算符:即咱们平常说的加减乘除运算,这种运算是分先后顺序的,如果想要优先进行运算,建议加上小括号,使其运 ...
- 解决Maven项目中pom.xml文件报错(Failure to transfer....)的问题
打开pom.xml文件,查看错误,如果错误类型为:Failure to transfer.........之类的,则表明你的pom中依赖的jar包没有完全下载. 解决方法:找到你本地的maven仓库, ...
- JZOJ 【NOIP2016提高A组集训第16场11.15】SJR的直线
JZOJ [NOIP2016提高A组集训第16场11.15]SJR的直线 题目 Description Input Output Sample Input 6 0 1 0 -5 3 0 -5 -2 2 ...