前言

继续总结吧,没有面试就继续夯实自己的基础,前阵子的在面试过程中遇到的各种问题陆陆续续都会总结出来分享给大家,这次要说的也是面试中被问到的一个高频的问题,我当时其实没答好,因为很早之前是看到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就帮助我们完成了自动装配。

你来说一下springboot的启动时的一个自动装配过程吧的更多相关文章

  1. SpringBoot项目启动时链接数据库很慢

    SpringBoot项目启动时链接数据库很慢 springboot项目在启动时候,如下图所示,链接数据库很慢 解决方法:在mysql 的配置文件中 配置 skip-name-resolve

  2. SpringBoot在启动时的多环境配置以及加载顺序

    通常我们在开发完成一个SpringBoot项目时,总是要打包部署的. 在启动SpringBoot应用时,我们常常会使用命令java -jar xxx.jar来启动这个服务. 命令java -jar 除 ...

  3. springBoot启动时让方法自动执行的几种实现方式

    一.开篇名义 在springBoot中我们有时候需要让项目在启动时提前加载相应的数据或者执行某个方法,那么实现提前加载的方式有哪些呢?接下来我带领大家逐个解答 1.实现ServletContextAw ...

  4. SpringBoot程序启动时执行初始化代码

    因项目集成了Redis缓存部分数据,需要在程序启动时将数据加载到Redis中,即初始化数据到Redis. 在SpringBoot项目下,即在容器初始化完毕后执行我们自己的初始化代码. 第一步:创建实现 ...

  5. SpringBoot程序启动时在Oracle数据库中建表充值

    例子工程下载链接:https://files.cnblogs.com/files/xiandedanteng/gatling20200428-1.zip 需求:在工程启动时在Oracle数据库中建表. ...

  6. springBoot和MyBatis整合中出现SpringBoot无法启动时处理方式

    在springBoot和Myatis   整合中出现springBoot无法启动   并且报以下错误 Description: Field userMapper in cn.lijun.control ...

  7. Oracle 12c启动时PDBs的自动打开

    Pluggable Database(PDB)为Oracle 12c中的一个重要的新特性, 但启动12c实例时并不会自动打开PDB数据库,这样,在启动实例后必须手动打开PDBs. 1. 实例启动后,手 ...

  8. SpringBoot:认认真真梳理一遍自动装配原理

    前言 Spring翻译为中文是“春天”,的确,在某段时间内,它给Java开发人员带来过春天,但是随着我们项目规模的扩大,Spring需要配置的地方就越来越多,夸张点说,“配置两小时,Coding五分钟 ...

  9. 天天用SpringBoot居然还不知道它的自动装配的原理?

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

随机推荐

  1. xss攻击与防范

    xss攻击方式以及防范 通常来说,网站一般都是有着,用户注册,用户登录,实名认证等等这些需要用户把信息录入数据库的接口 xss找的就是这种接口,他们可以在传递数据的时候,传递恶意的  script  ...

  2. 深度分析:SpringBoot异常捕获与封装处理,看完你学会了吗?

    SpringBoot异常处理 简介 ​ 日常开发过程中,难免有的程序会因为某些原因抛出异常,而这些异常一般都是利用try ,catch的方式处理异常或者throw,throws的方式抛出异常不管.这种 ...

  3. 通过Folx的排序功能来设置下载任务的优先级

    当我们使用Folx进行多任务下载时,突然遇到要下载一个紧急文件的情况,该如何让这个紧急文件的下载任务排在优先的位置?当然,用户也可以先暂停所有的下载任务,仅开启紧急文件的下载任务. 但这种方式需要用户 ...

  4. 让mac电脑更简单运行Windows软件的CrossOver,优势知多少?

    如今,一些iPhone和iPad机型拥有Face ID功能,此功能作用允许用户通过面部识别来解锁设备.该功能还不能在Mac上使用,但是国外媒体于7月27日报道称,在公测第三版的macOS Big Su ...

  5. 使用ABBYY FineReader将文档保存为电子书形式

    运用ABBYY FineReader 15的OCR识别技术,不仅能将PDF文档.图像.扫描页面等保存为可编辑的格式,方便用户的进一步编辑使用:而且还能直接转换为电子书的格式,方便用户使用更为便携的电子 ...

  6. 网络系列之 jsonp 百度联想词

    jsonp 可以跨域,ajax 不可以,ajax 会受到浏览器的同源策略影响,何为同源策略? 同源策略就是,如果 A 网站 想拿 B网站里的资源, 那么 有三个条件, 你得满足才能拿. 第一个:域名相 ...

  7. Kafka入门之broker-消息设计

    消息设计 1.消息格式 Kafka的实现方式本质上是使用java NIO的ByteBuffer来保存消息,同时依赖文件系统提供的页缓存机制,而非依靠java的堆缓存. 2.版本变迁 0.11.0.0版 ...

  8. macOS tips

    1.设置常用linux命令的快捷键 打开terminal command+space,搜索"terminal"关键字 进入"~/"目录 cd ~/ touch ...

  9. Docker Vs Podman

    翻译自 Chetansingh 2020年4月24日的博文<Docker Vs Podman> [1] 容器化的一场全新革命是从 Docker 开始的,Docker 的守护进程管理着所有的 ...

  10. 【刷题笔记】DP优化-状压

    因为篇幅太长翻着麻烦,计划把DP拆成几个小专题,这里原文只留下状压,其他请至后续博文. 状态压缩优化 所谓状态压缩,就是将原本需要很多很多维来描述,甚至暴力根本描述不清的状态压缩成一维来描述. 时间复 ...