springboot启动自动装配过程

1、前言
面试中的一个高频的问题,即springboot启动过程。
2、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就帮助我们完成了自动装配。

猜你喜欢

转载自blog.csdn.net/qq_45793102/article/details/115438998
今日推荐