玩转 Spring Boot 原理篇(自动装配源码剖析)

0. 

3f2aeeda9f5601df2a4c63c03971d8a7.png

0.0. 历史文章整理

玩转 Spring Boot 入门篇

玩转 Spring Boot 集成篇(MySQL、Druid、HikariCP)

玩转 Spring Boot 集成篇(MyBatis、JPA、事务支持)

玩转 Spring Boot 集成篇(Redis)

玩转 Spring Boot 集成篇(Actuator、Spring Boot Admin)

玩转 Spring Boot 集成篇(RabbitMQ)

玩转 Spring Boot 集成篇(@Scheduled、静态、动态定时任务)

玩转 Spring Boot 集成篇(任务动态管理代码篇)

玩转 Spring Boot 集成篇(定时任务框架Quartz)

玩转 Spring Boot 原理篇(源码环境搭建)

玩转 Spring Boot 原理篇(核心注解知多少)

玩转 Spring Boot 原理篇(自动装配前凑之自定义Starter)

0.1. Spring Boot 自动装配原理

3e8ea4f5694ef1efd16e6c983c98937d.png

  • Spring Boot 通过 @EnableAutoConfiguration 注解开启自动配置,@EnableAutoConfiguration 注解可以帮助 SpringBoot 应用将所有符合条件的 @Configuration 配置都加载到 IOC 容器,就跟“八爪鱼”一样。

  • 借助 Spring 框架工具类 SpringFactoriesLoader 的支持,可以智能的加载 META-INF/spring.factories 配置文件中注册的各种 XxxAutoConfiguration 类。

  • 当某个 XxxAutoConfiguration 类满足其注解 @Conditional 指定的生效条件时,实例化该 XxxAutoConfiguration 类中定义的 Bean 注入 Spring 容器,就可以完成依赖框架的自动装配。

1. Spring Boot 源码剖析

Spring Boot应用启动时会调用 SpringApplication.run(String... args) 方法。

6f4c7ee49faf7a8f54afe913b1d4dc30.png

run 方法内部会调用 refreshContext(context) 方法来完成自动装配的操作。

971f3f7da29af4e01a5043b6627e8812.png

跟剥洋葱一样,通过 debug 的方式一层层往里跟,最终会调用ConfigurationClassParser 类来完成所有配置类的解析,其所有的解析逻辑在 parser.parse(candidates) 方法中。

35a3a0eed95d87930d8aad3dec3f7945.png

调用重载的 parse 方法来完成解析。

b7cfcc26f761c1087641b452a2053069.png

接着调用  processConfigurationClass 方法来完成配置类解析处理。

bd5fac46eb653e86e250d5455fb4940a.png

接着调用 doProcessConfigurationClass 方法,此方法是支持注解配置的核心逻辑处理,方法内部会调用 processImports(configClass, sourceClass, getImports(sourceClass), filter, true) 方法进行递归解析,获取导入的配置类(@Import)。

7c753ed5f31cd4409b7750534f9a5eea.png

接着看看获取配置类的相关逻辑,此时会看到所有的 bean 都以导入的方式被加载进去。

8d89d2c5893a312fc27789621482ae68.png

继续 Debug,会调用 ConfigurationClassParser 中的 parse 方法中的最后一行,继续跟进 this.deferredImportSelectorHandler.process() 方法。

3aa28d3ccc56549b02abd7f33e4a8382.png

继续 Debug,会调用 DeferredImportSelectorHandler 的 process 方法。

d5b8f8baf9206a4cfd904a760ae8b033.png

继续 Debug,会调用 DeferredImportSelectorGroupingHandler 的 processGroupImports 方法。

1a40a81cc9c6246f0320a678b1b4c828.png

processGroupImports() 内部,会调用 grouping.getImports() 方法。

79007d76f1bac110d1593d9170563703.png

grouping.getImports()方法内部会调用 this.group.process方法。

e6ea0aa19363f9796792cf2e45ac58ab.png

this.group.process 方法可以说是自动装配的重点了。

588266ae4ad562bb0528618d1c055b2b.png

getAutoConfigurationEntry 方法详细解释一下

/**
 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
 * of the importing {@link Configuration @Configuration} class.
 * @param annotationMetadata the annotation metadata of the configuration class
 * @return the auto-configurations that should be imported
 */
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   // 通过 SpringFactoriesLoader 类提供的方法加载类路径中的 META-INF 目录下的 
   // spring.factories 文件中针对 EnableAutoConfiguration 的注册配置类
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   // 对获得的注册配置类集合进行去重处理,防止多个项目引入同样的配置类
   configurations = removeDuplicates(configurations);
   // 获得注解中被 exclude 或 excludeName 所排除的类的集合
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   // 检查被排除类是否可实例化,是否被自动注册配置所使用,不符合条件则抛出异常
   checkExcludedClasses(configurations, exclusions);
   // 从自动配置类集合中去除被排除的类
   configurations.removeAll(exclusions);
   // 检查配置类的注解是否符合 spring.factories 文件中 AutoConfigurationImportFilter 指定的注解检查条件
   configurations = getConfigurationClassFilter().filter(configurations);
   // 将筛选完成的配置类和排查的配置类构建为事件类,并传入监听器。监听器的配置在于 spring.factories 文件中
   // 通过 AutoConfigurationImportListener 指定
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

能够发现getCandidateConfigurations 方法中会通过 SpringFactoriesLoader 类来加载类路径中的 META-INF 目录下的 spring.factories 文件中针对 EnableAutoConfiguration 的注册配置类。

d15f69ba1001375d236e0e0b6a1fbaa3.png

为了调试方便,在源码中的 process 方法上里加入了打印输出。

ddce28de30231143850f2b7cace8e6ed.png

运行后,此时控制台输出如下。

AutoConfigurationImportSelector.AutoConfigurationGroup.process() , entries = {
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration=smoketest.simple.SampleSimpleApplication, 
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration=smoketest.simple.SampleSimpleApplication, 
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration=smoketest.simple.SampleSimpleApplication, 
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration=smoketest.simple.SampleSimpleApplication, 
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration=smoketest.simple.SampleSimpleApplication, 
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration=smoketest.simple.SampleSimpleApplication, 
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration=smoketest.simple.SampleSimpleApplication, 
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration=smoketest.simple.SampleSimpleApplication, 
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration=smoketest.simple.SampleSimpleApplication, 
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration=smoketest.simple.SampleSimpleApplication, 
org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration=smoketest.simple.SampleSimpleApplication, 
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration=smoketest.simple.SampleSimpleApplication, 
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration=smoketest.simple.SampleSimpleApplication, 
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration=smoketest.simple.SampleSimpleApplication
}

2. 例行回顾

本文采取 Debug 的方式跟了一下 Spring Boot 自动装配的源码,旨在感受一下自动装配的实现方式,其实这种自动装配的思想,在开发轮子时或许能够借鉴一下,会对轮子的扩展带来质的改变。

为了方便记忆,把 Spring Boot 自动装配繁琐的流程抽象一下。

5a3fb1d6c8ee2202bbf488ecbd1a9716.png

另外 Spring Boot 自动装配源码 Debug 主线,感兴趣可以自行跟一下源码。

83629f23e88dd909683f8f803a8d3d51.png

一起聊技术、谈业务、喷架构,少走弯路,不踩大坑,会持续输出更多精彩分享,欢迎关注,敬请期待!

f0248d10149a39d9f069d9f4cbfb0c8f.jpeg

参考资料:

https://spring.io/

https://start.spring.io/

https://spring.io/projects/spring-boot

https://github.com/spring-projects/spring-boot

https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/

https://stackoverflow.com/questions/tagged/spring-boot

《Spring Boot实战》《深入浅出Spring Boot 2.x》

《一步一步学Spring Boot:微服务项目实战(第二版)》

《Spring Boot揭秘:快速构建微服务体系》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值