springboot自动装配源码详解

springboot 中非常重要的一个概念就是自动装配,本文详细介绍下自动装配的源码。

简单介绍下自动装配:springboot 可以自动将第三方包中的配置类,纳入 ioc 容器中进行管理。

平时我们使用 springboot 时,只需要在 pom 中引入相应的 maven 坐标,比如引入 spring-boot-starter-data-redis,我们直接就能使用 RedisTemplate 等 bean 实例进行操作了,根本不需要我们配置对应的 bean。接下来通过源码来解释这个原因。

自动装配中一个非常非常重要的类:AutoConfigurationImportSelector

这个类是在 @EnableAutoConfiguration 注解上 Import 的,而我们启动类的注解 @SpringBootApplication 中,包含了 @EnableAutoConfiguration 注解。所以,根据我前面写的文章,ioc 容器启动时,也必然会将 AutoConfigurationImportSelector 这个类纳入管理。

我们通过代码来看下这个类是在哪里开始工作的:

来到前面的文章《spring ioc源码讲解之加载BeanDefinition》中讲过的 ConfigurationClassParser 类的 parse 方法中,红框中的 parse 方法就是之前讲过的加载 @ComponentScan 扫描的类,以及对 @Import 和 @Bean 做预处理的地方。最下面的 deferredImportSelectorHandler.process() 就是导入第三方类的地方。

由于 springboot 自动装配,是通过 @Import 注解的,所以首先进入 parse 方法看下如何对 @Import 做预处理。

这个前面讲过,是在 processImports 方法中处理的。注意,我们要看的是 sourceClass 为启动类(SampleApplication) 的场景,因为 AutoConfigurationImportSelector 是通过启动类最终导入的。进入 processImports 方法:

启动类导入了两个类,其中一个是 AutoConfigurationImportSelector,它是 DeferredImportSelector 类型的,所以会调用 deferredImportSelectorHandler 的 handle 方法。

在这个方法中,往 deferredImportSelectors 中设置了一个 DeferredImportSelectorHolder。后面会用到 

接下来进入 deferredImportSelectorHandler.process() 看下是如何导入第三方类的。

在这里就用到了刚刚设置的 deferredImportSelectors。然后创建了一个 DeferredImportSelectorGroupingHandler 对象,并调用了该 handler 对象的 register 方法:

在这个 register 中就从刚刚的 DeferredImportSelectorHolder 中取出里面的 AutoConfigurationImportSelector,并设置到 groupings 这个集合中,后面会用到。

接下来就是调用 handler 对象的 processGroupImports 方法了,真正处理导入就是在这个方法中进行的!

看到了吧,这里就遍历 groupings 来做处理了。 此时只有一个元素,就是 AutoConfigurationImportSelector。接下来就是最最最关键的一步了:grouping.getImport() 方法。

进入 getImports 方法

这个 this.group 就是 AutoConfigurationImportSelector,调用它的 process 方法

此时进入了 AutoConfigurationImportSelector 中的一个静态类 AutoConfigurationGroup 的 process 方法中。关键的地方是 getAutoConfigurationEntry 方法。

在这个方法中,首先通过 getCandidateConfigurations 方法获取候选的组件列表。进入这个方法看下

在这个方法中,使用了一个在 springboot 自动装配中,一个非常重要的方法:SpringFactoriesLoader.loadFactoryNames

我们先看下这个 SpringFactoriesLoader.loadFactoryNames 做了些啥:

它有两个参数,首先是它的第一个参数:EnableAutoConfiguration.class

第二个参数是一个类加载器。

通过下面那行日志,也可以猜出来,这个方法的功能是从 META-INF/spring.factories 文件中,获取自动配置类,且支持自定义的包(只要我们自定义的包中也有 META-INF/spring.factories 这个文件)

接着往下看,进入 loadSpringFactories 方法

这个时候,发现缓存中已经有东西,而且里面包含了 EnableAutoConfiguration。说明这个方法在前面某个地方已经调用过一次了。

那么,第一次调用并设置这个 cache 的地方又是在哪里呢?通过在这个地方加上断点,然后重新 debug 发现,其实在 springboot 启动时,new SpringApplication 时就已经调用过一次了!

具体看下这个 loadSpringFactories 方法:

它从我们的所有包中获取 META-INF/spring.factories 文件对应的 url,然后循环遍历 url,解析此 url 对应的 spring.factories 文件。

比如第一个解析了 spring-boot-2.5.3.jar 中的 META-INF/spring.factories,得到一个 map,接下来解析 spring-boot-autoconfigure-2.5.3.jar 中的 META-INF/spring.factories。解析出来的内容会跟之前的进行合并。如果多个包的 spring.factores 文件中都配置了 org.springframework.boot.autoconfigure.EnableAutoConfiguration,那么两个文件中配置的内容会加起来。

这个操作是第一次调用 SpringFactoriesLoader.loadFactoryNames 方法时执行的。后面再继续调用就直接从缓存中取了!

继续回到我们前面的操作,将这个 map 返回,然后根据 "EnableAutoConfiguration" 这个 key,从 map 中获取集合了。

然后获得133个配置类的全路径,经过去重,取出需要排除的,然后剩余133个。接下来有一步重要的操作:filter。

在 spring-boot-autoconfigure 包的 META-INF/spring.factories 文件中,添加了几乎我们叫得出名字的所有配置类,在前面的 getCandidateConfigurations 方法中会全部获取到。很显然,里面很多类是我们不需要导入的,比如这个工程我压根没引入 redis、mongo、elasticsearch 包的依赖,所以相关的配置类也不需要加载,否则浪费时间。那这个方法就是用来过滤掉我们不需要加载的类的。具体是怎么实现的就不深究了。

过滤完成后,只剩下32个元素了。

之后调用了 fireAutoConfigurationImportEvents 方法,发送事件。这里的时间监听器是 ConditionEvaluationReportAutoConfigurationImportListener,调用它的 onAutoConfigurationImportEvent 方法,将这32个类的全限定名缓存到 unconditionalClasses 集合中。

回到 getAutoConfigurationEntry 方法中,最终返回一个 AutoConfigurationEntry 对象,里面携带了这32个需要加载的类的全限定名。

回到 process 方法,循环将这个32个放入 entries 这个 map 中缓存起来。

接着继续往下执行,group.process 已经执行完毕了,它最终在 entries 缓存了需要加载的32个类全限定名。然后执行 selectImports() 方法。

这个方法也是在 AutoConfigurationImportSelector 类中的。

在里面进行了简单的过滤操作,然后对配置类做了一下排序,最终将每个类封装成 Entry 对象,并返回 Entry 集合。回到前面调用 grouping.getImports 的地方,对刚刚获取的类继续进行解析并加入 configurationClass 集合中,后面会用。

parse 结束,从parser 中 取出前面得到的 configurationClass 集合。

这里看到有72个,比我们预想中的要多不少。是由于有些类同时生成了代理类,或者其他一些原因。

接下来,就是调用 reader.loadBeanDefinitions,对这些类进行加载并生成 BeanDefinition 了!这部分在本文就不讲了,在我之前的文章《spring ioc源码讲解之加载BeanDefinition》中,已经详细地讲解了。通过 debug 也可以看到,loadBeanDefinitions 方法调用结束之后, beanDefinitionMap 中从原来的11个元素,变成了183个元素。

为什么又变多了呢?那是因为导入的第三方包中的类,基本都是些配置类,里面本身就通过 @Bean 也定义了 bean。前面的文章中讲过,通过 @Bean 注解定义的 bean,和通过 @Import 导入的类,以及 springboot 自动装配的类,都是在这个 loadBeanDefinitions 方法中加载的!例如通过自动装配导入的类 RedisAutoConfiguration,它里面就通过 @Bean 定义了 redisTemplate 和 stringRedisTemplate 这两个 bean。这也就解释了为什么我们只需要引入 redis 的包,就能直接使用 redisTemplate 进行操作。

到这里为止,springboot  的自动装配应该就算是完成了,后面的事情属于 spring ioc 的流程了(bean 的实例化、初始化等操作)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值