SpringBoot自动配置

首先,你在pom文件里引入的很多第三方jar里都有一个文件 META-INF/spring.factories,这个文件里的内容和关系到能否自动配置, 那有的jar为啥没有,是不需要SpringBoot来自动配置吗,这个我们后面再说。

 

 

 

 

先来看一下redisson starter的/ META-INF/spring.factories是怎么写的, 如下图,只有一行,是key=value的格式

 

 这个key是一个spring的注解,value是一个redission的jar包里的一个配置类, 如下图

 

根据注解的名字,大胆猜测一下,这个配置类是需要被自动配置的

事实上就是这样的,SpringBoot会扫描所有classpath下的所有jar里面的spring.factories文件,然后生成一个大概就是Map<String, List<String>>形式的一个键值对,value会和相同key的value组成一个List。

那就可以根据key是EnableAutoConfiguration,得到所有需要自动配置的配置类的信息了,到时候只需要把每个配置类配置一下就行了

 

具体怎么做的呢?

 

首先我们SpringBoot项目默认都是有Application类,上面还有个注解@SpringBootApplication,这是个组合注解,是由好多个别的注解组合而成,因为我们是想研究自动配置,那我们着重关注

@SpringBootConfiguration    这个注解代表这个是源配置文件
@EnableAutoConfiguration   这个代表Spring能进行自动配置,既是一个开关,也是指引Spring怎么去找到并进行自动配置, 它本身也是个组合注解,点开来看又用@import注解引入了一个class
@ComponentScan                这个会告诉Spring扫描哪些package下的类,这里默认会扫描Application所在的package下

 

打开AutoConfigurationImportSelector.class看, 它实现了接口 DeferredImportSelector,DeferredImportSelector接口又是继承自ImportSelector,

DeferredImportSelector翻译一下就是 推迟的引入选择器,从名字上来看,它会帮助Spring引入bean或者配置,而且是推迟的

这是因为Spring会先扫描我们代码中的(即和Application类相同package下的)配置类,然后根据代码中的一些bean生成对应的BeanDefinition,然后才是引入依赖的配置类,并根据依赖里面的bean来生成Beandifinition,后面再统一将BeanDefinition实例化成bean交给IoC容器。

为啥要这么先处理自己代码包下的配置类和bean?网上找了一下答案,说是方便我们扩展和覆盖。好像没毛病,肯定是依赖里面帮你配置好了bean,如果你自己不配置的话,就直接用依赖包里写好的,但是你如果自己写了,依赖包里配置好的bean就不使用了。符合SpringBoot的约定优于配置。

 

 比如我们如果引入了redisson的starter并在application.properties里面配置一下连接参数,就能在Service中或者Component中使用@Resource或者@Autowired来注入RedissonClient这个bean了,这个bean是怎么会被IoC容器实例化并存在容器中的呢?

 我们再次打开redisson--spring-boot-starter的spring.factories和它里面指定的RedissonAutoConfiguration这个配置类来看一下,如下图

 

 

通过SpringBoot的自动配置,先把RedissonAutoConfiguration这个配置类通过spring.factories暴露给Spring,然后Spring根据这个配置类,调用了下面很多个@Bean注解的方法,把这些实例给加到IoC容器中。

@ConditionalOnMissionBean(RedissonClient.class)的意思就是如果我们自己的代码中没有引入这个bean,那就会帮我们把RedissonClient给实例化并交给IoC,

假如我们自己想要自己实例化一个特殊的RedissonClient或者在实例化前做点操作,那我们可以自己写一下,如下图,这样也就是实现了上面提到的 方便我们扩展和覆盖

 

 

回到上面的一个问题,为什么有的starter里面没有spring.factories呢?那它是怎么实现自动配置的。

是因为有个我们把我们的项目的parent设置成了spring-boot-starter-parent,springboot自动帮我们依赖了spring-boot-autoconfigure.jar

 这里面有两个重要的文件,一个是配置了当前SpringBoot版本常用的依赖的配置类,一个是配置类的一些加载条件,你看我在里面找到了啥,是spring-boot-starter-data-redis里面的配置类,之所以starter的jar里面没有spring.factories文件,原来是在这里统一配置了。

 

 

 

最后我们再来看一下调用的大概过程

从@SpringBootApplication =》 @EnableAutoConfiguration =》 @Import({AutoConfigurationImportSelector.class}),看一下这个class的一个重要方法getAutoConfigurationEntry

 

 这个方法就是放回所有的配置类,先通过SpringFactoriesLoader去获取依赖的jar里的配置类,然后获取了从autoconfigure这个jar的AutoCOnfiguration.imports文件里找到的所有配置类,加在一起作为候选名单,经过去重,filter(根据spring-autoconfiguration-metadata.properties里的条件)等操作,返回出去需要自动配置的一些配置类。

 

然后在引入这些配置类的时候,根据@Conditional***的注解,按要求把需要的bean都给实例化加到IoC容器。

 

 

大概的调用路径(基于SpringBoot 2.7):

SpringAppLication.run
SpringAppLication.refreshContext
SpringAppLication.refresh
ServletWebServerApplicationContext.refresh
AbstractApplicationContext.invokeBeanFactoryPostProcessors
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors
PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry
ConfigurationClassPostProcessor.processConfigBeanDefinitions
ConfigurationClassParser.parse
DeferredImportSelectorHandler.process
DeferredImportSelectorGroupingHandler.processGroupImports
DeferredImportSelectorGrouping.getImports
AutoConfigurationGroup.process
AutoConfigurationImportSelector.getAutoConfigurationEntry
AutoConfigurationImportSelector.getCandidateConfigurations
SpringFactoriesLoader.loadFactoryNames 和 ImportCandidates.load

 

自动配置大概就是这样。

 

 

提一下条件配置,随便裂了几个常见的

ConditionalOnMissingBean    如果后面指定的这个bean还没有加入到IoC容器的话,就把这个注解标注的方法执行一下
ConditionalOnMissingClass   同上,只不过变成了class,猜测应该是有没有类加载器加载了这个class
ConditionalOnBean                某个bean已经被加入IoC了,才能执行这个方法
ConditionalOnClass                某个class已经被类加载了,才能执行这个方法

 

SPI和spring.factories其实比较像,都是约定了一个文件位置,然后读这个文件来实现自动扩展,动态加载一些内容。有兴趣的自己查一下。

 


 

posted @ 2024-03-14 22:55  坏男银  阅读(38)  评论(0编辑  收藏  举报