SpringBoot bean自动装配原理,这一篇就 够了! 3

简介: SpringBoot bean自动装配原理,这一篇就够了!

7. 自动装配源码分析

终于来到了大家喜闻乐见的部分:源码分析

在我们前面6节学习了各种”招式“之后,让我们请出对手:SpringBoot

现在在你面前的是一个SpringBoot”空项目“,没有添加任何依赖包和starter包

启动项目:

正常启动,让我们从@SpringBootApplication开始研究

7.1 @SpringBootConfiguration

会看到@SpringBootApplication这个注解由好多注解组成

主要的有以下三个:

    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan

    先来看第一个:@SpringBootConfiguration

    进入这个注解之后会发现

    原来你就是一个@Configuration啊,一个JavaConfig配置类

    那我们使用JavaConfig不就是用来配置bean吗,所以有了这个注解之后我们可以在SpringBoot运行的主类中使用@Bean标签配置类了,如下图所示:

    7.2 @ComponentScan

    这个注解相信大家都认识了,组件扫描

    这个扫描的范围是:SpringBoot主启动类的同级路径及子路径

    7.3 @EnableAutoConfiguration

    来看这个注解,也是最核心的内容

    这个注解怎么这么眼熟啊,还记得刚才的@MyEnableAutoConfig注解吗?就是我们自己写的那个注解

    进入@EnableAutoConfiguration:

    看图中红圈位置的注解:@Import(AutoConfigurationImportSelector.class)

    是不是跟我们上面自己写的内容一样!

    这里的作用便是导入了 AutoConfigurationImportSelector 这个类的bean定义

    我们都知道,如果这个类实现了ImportSelector接口,那他肯定重写了一个方法,就是我们上面重写过的selectImports方法:

    果然,在这个类里面确实有这个selectImports方法:

    我的天,好长的一串代码,一行都放不下!

    等等等等,这个类我们当时返回的是什么?是一个字符串数组String[ ],那这个类无论多么长,返回的肯定就是一个字符串数组,不信你自己看:

    这个字符串数组存放的内容我们是否清楚呢?当然清楚了!我们返回的是要加载的Config配置文件的全包名,通过返回这个全包名,我们就能自动装配上这些配置文件下定义的bean对象,从而达到了自动装配的目的!

    根据刚才我们自己实现的selectImports方法,我们是通过注解类的名字来查找,并且最终得到需要加载的Config类的全类名,最后返回的。

    因此,这里必然有一个根据注解类名字来查找相应的Config文件的操作

    我们继续反推,看到返回时的定义如下:

    我们发现autoConfigurationEntry中保存着我们需要的配置信息,它是通过getAutoConfigurationEntry方法获取的,于是我们继续深入,进入getAutoConfigurationEntry方法

    这一段代码真是把人难住了,好大一片,不知道在做什么

    此时此刻,我又回想起了在家乡的母亲,夏天的蝉鸣,池塘的荷花…

    回家!有了!我们先想这个方法应该返回什么,根据我们前面的经验,这里应该返回一个类似于Entry的保存了我们需要的配置信息的对象

    这个方法返回的是新建的AutoConfigurationEntry对象,根据最后一行的构造函数来看,给他了两个参数:


    configurations, exclusions

    configurations显然使我们需要的配置文件,也是我们最关心的,而exclusions字面意思是排除,也就是不需要的,那我们接下来应该关注configurations到底是怎么来的

    根据我们前面的经验,我们是根据注解类名来从一个配置文件中读取出我们需要的Config配置类,这里configurations就代表了Config配置类,那么我们应该找到一个入口,这个入口跟注解相关,并且返回了configurations这个参数。

    正如我们所料,这个方法的参数确实传递过来了一个东西,跟注解有关:

    看见那个大大的Annotation(注解)了吗!

    那么根据这条”线索“,我们按图索骥,找到了三行代码,范围进一步缩小了!

    此时再加上返回了configurations,我们最终确定了一行代码:

    就是这个getCandidateConfigurations方法,符合我们的要求!

    从字面意思上分析,获取候选的配置,确实是我们需要的方法

    OK,让我们继续前进,进入这个方法:

    这个方法是不是也似曾相识呢?我们之前写过一个专门用于读取配置文件的类MyPropertyReader,还记得吗?

    如果你还记得的话,我们自己写的工具类里面也是一个静态方法readPropertyForMe来帮我读取配置文件

    但是我们的配置文件路径一定是需要指定的,不能乱放。

    从这个loadFactoryNames方法体来看,好像没有给他传递一个具体路径

    但是从下面的Assert断言中,我们发现了玄机:

    在META-INF/spring.factories文件中没有找到自动配置类Config,你要检查balabala。。。。

    根据我不太灵光的脑袋的判断,他的这个配置文件就叫spring.factories,存放的路径是META-INF/spring.factories

    于是我们打开spring boot自动装配的依赖jar包:

    那这个配置文件里面的内容,是不是跟我们想的一样呢?

    原来如此。

    这里的EnableAutoConfiguration注解,正是我们此行的起点啊…

    到这里,自动装配到底是什么,应该比较清楚了,原来他是帮我们加载了各种已经写好的Config类文件,实现了这些JavaConfig配置文件的重复利用和组件化

    7.4 loadFactoryNames方法

    行程不能到此结束,学习不能浅尝辄止。

    我们还有最后一块(几块)面纱没有解开,现在还不能善罢甘休。

    让我们进入loadFactoryNames方法:

    这个方法非常简短,因为他调用了真正实现的方法:loadSpringFactories

    这一行return代码我复制在下面:

      loadSpringFactories(classLoader)   
      .getOrDefault(factoryTypeName, Collections.emptyList());

      可以分析得出:loadSpringFactories方法的返回值又调用了一个getOrDefault方法,这明显是一个容器类的方法,目的是从容器中拿点东西出来

      就此推测:loadSpringFactories返回了一个包含我们需要的Config全类名(字符串)的集合容器,然后从这个集合容器中拿出来的东西就是我们的configurations

      让我们看这个loadSpringFactories方法:

      它确实返回了一个容器:Map<String, List> 这个容器的类型是:MultiValueMap<String, String>

      这个数据结构就非常牛逼了,多值集合映射(我自己的翻译)简单来说,一个key可以对应多个value,根据他的返回值,我们可以看到在这个方法中一个String对应了一个List

      那么不难想到MultiValueMap中存放的形式:是”注解的类名——多个Config配置类“ 让我们打个断点来验证一下:

      果然是这样,并且@EnableAutoConfiguration注解竟然加载了多达124个配置类!

      接下来我们继续思考:我们来的目的是获取configurations,所以无论你做什么,必须得读取配置文件,拿到configurations

      于是我们在try方法体中果然发现了这个操作:

      他获取了一个路径urls,那么这个路径是否就是我们前面验证的META-INF/spring.factories呢?

      我们查看静态常量FACTORIES_RESOURCE_LOCATION的值:

      果真如此,bingo!继续往下看,果然他遍历了urls中的内容,从这个路径加载了配置文件:终于看到了我们熟悉的loadProperties方法!

      那我们大概就知道了,他确实是通过找到路径,然后根据路径读取了配置文件,然后返回了读取的result

      这就是loadFactoryNames方法的内部实现。

      7.5 cache探秘

      到这里有的人又要问了:是不是结束了?其实还远没有!

      细心地朋友已经发现了玄机,隐藏在loadFactoryNames方法的开头和结尾:

      喂喂,这个返回的result好像并不是直接new出来的哦

      它是从cache缓存中取出来的,你发现了没有

      根据下面的if判断,如果从缓存中读取出来了result,并且result的结果不为空,就直接返回,不需要再进行下面的读写操作了,这样减少了磁盘频繁的读写I/O

      同理,在我更新完所有的配置文件资源之后,退出时也要更新缓存。

      7.6 getAutoConfigurationEntry再探

      关键部分已经过去,让我们反过头来重新审视一下遗漏的内容:

      还记得getAutoConfigurationEntry方法吗?

      我们最后来研究一下这个类除了getCandidateConfigurations还干了哪些事情:

      • removeDuplicates
      • configurations.removeAll(exclusions)

      可以看到,这里对加载进来的配置进行了去重、排除的操作,这是为了使得用户自定义的排除包生效,同时避免包冲突异常,在SpringBoot的入口函数中我们可以通过注解指定需要排除哪些不用的包:

      例如我不使用RabbitMQ的配置包,就把它的配置类的class传给exclude


      @SpringBootApplication(exclude = {RabbitAutoConfiguration.class})

      8. 自动装配本质

      我的理解:

      • SpringBoot自动装配的本质就是通过Spring去读取META-INF/spring.factories中保存的配置类文件然后加载bean定义的过程。
      • 如果是标了@Configuration注解,就是批量加载了里面的bean定义
      • 如何实现”自动“:通过配置文件获取对应的批量配置类,然后通过配置类批量加载bean定义,只要有写好的配置文件spring.factories就实现了自动。

      9. 总结

      Spring Boot的自动装配特性可以说是Spring Boot最重要、最核心的一环,正是因为这个特性,使得我们的生产复杂性大大降低,极大地简化了开发流程,可以说是给我们带来了巨大的福音了~~

      笔者本人对源码的理解仍然没有那么深刻,只是喜欢分享自己的一些学习经验,希望能和大家共同学习,毕竟掌握一门新技术的快感嘛… 大家都懂的!

      写这篇文章耗费了巨大的精力,每一个字均是手码,真的希望喜欢的朋友可以点赞收藏关注支持一波,这就是对我这个未出世的学生的最大激励了!

      最后,我画了一份Spring Boot自动装配详细流程图,分享给大家。

      Spring Boot自动装配详细流程图:



      相关文章
      |
      22小时前
      |
      Java Spring
      SpringBoot自动装配的原理
      在Spring Boot项目中,启动引导类通常使用`@SpringBootApplication`注解。该注解集成了`@SpringBootConfiguration`、`@ComponentScan`和`@EnableAutoConfiguration`三个注解,分别用于标记配置类、开启组件扫描和启用自动配置。
      29 17
      |
      3天前
      |
      Java
      SpringBoot构建Bean(RedisConfig + RestTemplateConfig)
      SpringBoot构建Bean(RedisConfig + RestTemplateConfig)
      24 2
      |
      21天前
      |
      Java Spring 容器
      springboot @RequiredArgsConstructor @Lazy解决循环依赖的原理
      【10月更文挑战第15天】在Spring Boot应用中,循环依赖是一个常见问题,当两个或多个Bean相互依赖时,会导致Spring容器陷入死循环。本文通过比较@RequiredArgsConstructor和@Lazy注解,探讨它们解决循环依赖的原理和优缺点。@RequiredArgsConstructor通过构造函数注入依赖,使代码更简洁;@Lazy则通过延迟Bean的初始化,打破创建顺序依赖。两者各有优势,需根据具体场景选择合适的方法。
      40 4
      |
      24天前
      |
      架构师 Java 开发者
      得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
      在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
      得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
      |
      2月前
      |
      Java 应用服务中间件 API
      Vertx高并发理论原理以及对比SpringBoot
      Vertx 是一个基于 Netty 的响应式工具包,不同于传统框架如 Spring,它的侵入性较小,甚至可在 Spring Boot 中使用。响应式编程(Reactive Programming)基于事件模式,通过事件流触发任务执行,其核心在于事件流 Stream。相比多线程异步,响应式编程能以更少线程完成更多任务,减少内存消耗与上下文切换开销,提高 CPU 利用率。Vertx 适用于高并发系统,如 IM 系统、高性能中间件及需要较少服务器支持大规模 WEB 应用的场景。随着 JDK 21 引入协程,未来 Tomcat 也将优化支持更高并发,降低响应式框架的必要性。
      Vertx高并发理论原理以及对比SpringBoot
      |
      1月前
      |
      Java Spring 容器
      Springboot3.2.1搞定了类Service和bean注解同名同类型问题修复
      这篇文章讨论了在Spring Boot 3.2.1版本中,同名同类型的bean和@Service注解类之间冲突的问题得到了解决,之前版本中同名bean会相互覆盖,但不会在启动时报错,而在配置文件中设置`spring.main.allow-bean-definition-overriding=true`可以解决这个问题。
      75 0
      Springboot3.2.1搞定了类Service和bean注解同名同类型问题修复
      |
      2月前
      |
      Java 开发者 数据格式
      【Java笔记+踩坑】SpringBoot基础4——原理篇
      bean的8种加载方式,自动配置原理、自定义starter开发、SpringBoot程序启动流程解析
      【Java笔记+踩坑】SpringBoot基础4——原理篇
      |
      2月前
      |
      Java Spring
      springboot 集成 swagger 2.x 和 3.0 以及 Failed to start bean ‘documentationPluginsBootstrapper‘问题的解决
      本文介绍了如何在Spring Boot项目中集成Swagger 2.x和3.0版本,并提供了解决Swagger在Spring Boot中启动失败问题“Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerEx”的方法,包括配置yml文件和Spring Boot版本的降级。
      springboot 集成 swagger 2.x 和 3.0 以及 Failed to start bean ‘documentationPluginsBootstrapper‘问题的解决
      |
      1月前
      |
      Java Shell C++
      Springboot加载注入bean的方式
      本文详细介绍了Spring Boot中Bean的装配方法。首先讲解了使用@Component、@Service、@Controller、@Repository等注解声明Bean的方式,并解释了这些注解之间的关系及各自适用的层次。接着介绍了通过@Configuration和@Bean注解定义Bean的方法,展示了其灵活性和定制能力。最后讨论了@Component与@Bean的区别,并提供了在Spring Boot应用中装配依赖包中Bean的三种方法:使用@ComponentScan注解扫描指定包、使用@Import注解导入特定Bean以及在spring.factories文件中配置Bean。
      |
      3月前
      |
      Java Spring 容器
      Java SpringBoot 中,动态执行 bean 对象中的方法
      Java SpringBoot 中,动态执行 bean 对象中的方法
      40 0
      下一篇
      无影云桌面