最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)

简介: 自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。

SpringBoot的自动装配原理是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文末说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。

一、自动配置概念

自动装配:遵循约定大约配置的原则,在boot程序启动后,起步依赖中的一些bean对象会自动注入到ioc容器。

  • 自动装配的例子:对于SpringBoot整合MyBatis时的自动配置效果,我们只需要引入MyBatis的起步依赖,这个时候像SqlSessionFactoryBean这样的bean对象就自动注入到ioc容器里面了,无需写响应的配置类。

二、半自动配置(误~🙏🙏

首先我们来看一下下面的代码是自动装配吗?

可以看到在启动类中我们需要自己写配置类,并手动使用@Import注解导入配置类。因此,这显然没有达到自动配置的效果。

那么下面我们通过翻看源码的方式,看看自动装配是怎么回事?


三、源码分析

1、验证DispatcherServlet的自动配置

在分析自动配置源码之前,我们将以SpringMVC中的DispatcherServlet类为例,来展示SpringBoot的自

动配置过程。首先验证一个问题:程序引入spring-boot-starter-web起步依赖,启动后,SpringBoot会自动往ioc容器中注入DispatcherServlet

这里我们在pom文件中仅引入了SpringBoot核心起步依赖spring-boot-starter,并没有引入web的起步依赖

<dependencies>
    <!-- SpringBoot核心起步依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

在启动类SpringbootAutoConfigApplication中,我们尝试从ioc容器中获取dispatcherServlet,输出一下。

@SpringBootApplication
public class SpringbootAutoConfigApplication {
    public static void main(String[] args) {
        // 获取run方法返回的ioc容器
        ApplicationContext context = SpringApplication.run(SpringbootAutoConfigApplication.class, args);
        // 尝试从ioc容器中获取dispatcherServlet
        System.out.println(context.getBean("dispatcherServlet"));
    }
}

结果如我们所想,Spring没有注册dispatcherServlet的bean对象,无法从ioc容器中获取该对象。

接下来我们引入spring-boot-starter-web起步依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

刷新maven后,再次运行发现可以获取到dispatcherServlet对象。

2、源码分析入口@SpringBootApplication

@SpringBootApplication是一个组合注解,它组合了@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个核心注解。

@Target(ElementType.TYPE) //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期,Runtime运行时
@Documented //表示注解可以记录在javadoc中
@Inherited  //表示可以被子类继承该注解
    
@SpringBootConfiguration  // 标明该类为配置类
@EnableAutoConfiguration  // 启动自动配置功能
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    ...
}

@SpringBootApplication注解的注解组成图:

其中@ComponentScan是bean扫描注解,我们主要来说一下 @SpringBootConfiguration 和 @EnableAutoConfiguration。

3、@SpringBootConfiguration的@Configuration

  • @SpringBootConfiguration也是一个组合注解,组合了@Configuration,说明我们的启动类也是一个配置类。
@Target(ElementType.TYPE) //元注解
@Retention(RetentionPolicy.RUNTIME) //元注解
@Documented //元注解
@Configuration  //核心注解
@Indexed  //spring5.0后新增的 索引注解,可以提高启动速度
public @interface SpringBootConfiguration {
  @AliasFor(annotation = Configuration.class)
  boolean proxyBeanMethods() default true;
}

4、@EnableAutoConfiguration的@AutoConfigurationPackage和@Import

  • @EnableAutoConfiguration是实现自动配置的核心注解,标识开启自动配置功能,是SpringBoot最重要的注解。同样作为组合注解,它组合了 @AutoConfigurationPackage 和 @Import。其中@Import导入注解导入了一个AutoConfigurationImportSelector.class。

从后缀可以看出,ImportSelector作为一个接口,@Import主要导入两种类,一种是配置类,一种是ImportSelector的实现类。

5、AutoConfigurationImportSelector类

接下来进入AutoConfigurationImportSelector类,它实现了DeferredImportSelector接口,而DeferredImportSelector接口继承了父接口ImportSelector。

也就是说AutoConfigurationImportSelector类也实现了ImportSelector接口,也就必然要重写selectImports()方法。这里使用Ctrl+F12快捷键,输入selectImports搜索该类中的方法。

6、selectImports()方法

selectImports()方法会被Spring自动地调用,该方法的作用是得到返回的全类名字符串数组,把这些全类名的bean对象注册到ioc容器里面。

这里说明一下,由于AutoConfigurationImportSelector没有直接实现ImportSelector接口,而是实现的DeferredImportSelector接口,SpringBoot对于DeferredImportSelector接口的实现类有另外一组处理流程,这个流程中并不会去调用selectImports()方法。虽然SpringBoot不会自动地去调用,但是我们通过这个selectImports()方法去翻看自动配置的原理是没有问题的。

7、getAutoConfigurationEntry()方法

SpringBoot并不会直接硬编码写死返回字符串数组,而是通过从配置文件中读取的方式返回字符串数组。接下来我们重点来看它读取的配置文件在哪里,最后一句中它将autoConfigurationEntry.getConfigurations()的结果作为参数转换为字符串数组。autoConfigurationEntry对象是通过上面的getAutoConfigurationEntry()方法得到的。

因此,我们猜想getAutoConfigurationEntry()方法里面应该需要知道配置文件在哪里,继续跟进这个方法。

这个方法的最后return了这个AutoConfigurationEntry对象,在构造这个对象时传入了两个参数configurations和exclusions,很明显configurations才和配置相关,而在上面通过getCandidateConfigurations()方法得到了configurations集合,所以我们继续观察getCandidateConfigurations()。

8、getCandidateConfigurations()方法

在getCandidateConfigurations()方法内部,ImportCandidates.load()方法加载了AutoConfiguration.class,下面还有一段断言,意思是加载后configurations不能为空,如果为空了就提示"No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.",即要在AutoConfiguration.imports找一些自动配置类,但是没有找到。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).getCandidates();
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

9、AutoConfiguration.imports配置文件

那这个.imports配置文件在哪里找呢?答案是从pom文件中的SpringBoot核心起步依赖spring-boot-starter找,Ctrl+左键点击跟进。

在它里面又引入了一个spring-boot-autoconfigure,autoconfigure顾名思义是自动配置。

此时我们从项目左侧External Libraries点开找到Maven: org.springframework,boot:spring-boot-autoconfigure:3.3.3,里面提供了一个jar包,在META-INF目录下的spring文件夹下,存放这org.springframework.boot.autoconfigure.AutoConfiguration.imports配置文件。

这个AutoConfiguration.imports配置文件就是SpringBoot自动配置的核心配置文件。点开可以发现里面存放着很多自动配置类的全类名。其中就有关于DispatcherServlet的自动配置类DispatcherServletAutoConfiguration。

10、DispatcherServletAutoConfiguration类

DispatcherServletAutoConfiguration这个类上添加了一个@AutoConfiguration,还有一个@ConditionalOnClass({DispatcherServlet.class})。

11、@AutoConfiguration和@ConditionalOnClass

在@AutoConfiguration注解内部也是一个组合注解,其中有@Configuration,也就是说明DispatcherServletAutoConfiguration是一个Spring的配置类。@Configuration封装在@AutoConfiguration中是为了更加见名知意,说明DispatcherServletAutoConfiguration这个类就是为了完成自动配置的。

另外,还有一个注册条件注解@ConditionalOnClass({DispatcherServlet.class}),意思是如果我们的环境里面有DispatcherServlet,则注入DispatcherServletAutoConfiguration的bean对象。反之如果是环境里面没有DispatcherServlet,DispatcherServletAutoConfiguration就不注入从而不生效。这也就是我们验证时为什么说只有引入了web起步依赖,SpringBoot才能帮我们自动注入DispatcherServlet。

12、DispatcherServletConfiguration内部类

源码我们再往下翻看,其中有一个内部类DispatcherServletConfiguration,@Configuration说明内部类也是一个配置类,它提供了一个方法,方法名为dispatcherServlet,返回值类型是DispatcherServlet,方法内部new了一个DispatcherServlet对象并返回,并且在方法上添加了@Bean注解。此时我们终于发现注入DispatcherServlet对象的地方在这里!

这里最核心的地方就在于,SpringBoot把这个自动配置类的全类名写到.imports文件里面了,SpringBoot就能够自动读取全类名,并把配置类对象注册到Spring的IOC容器里面。又由于配置类内部还有配置类,而且内部配置类里面还有一些方法添加了@Bean注解,Spring会继续解析,直到把这些有@Bean的方法都解析到并执行方法,最后把返回值注入到ioc容器里面。

以上就是通过深入分析SpringBoot源码,来理解自动装配的原理。


四、不同版本SpringBoot自动配置的区别

特别注意,当前网上的大部分自动装配原理的分析文章都是基于SpringBoot3以下,使用的是spring.factories作为配置文件进行自动装配,SpringBoot3以上的版本自动配置读取的核心配置文件为:AutoConfiguration.imports。

  • springboot2.7版本以前,自动配置使用的配置文件为:spring-factories,会从这个文件中读取自动配置类的全类名
  • springboot2.7到3.0以前,同时兼容了AutoConfiguration.imports和spring-factories文件
  • springboot3.0以后,自动配置只支持AutoConfiguration.imports文件

那么上图对于如何改造之前非自动装配的案例给出了基本的步骤。相信学习完上面的SpringBoot自动装配原理后,会思考如何将自己的模块改造成SpringBoot可以自动装配的模块,这也就是如何自定义starter。


五、面试话术:说一说SpringBoot自动配置原理?

SpringBoot自动装配的原理:首先在主启动类添加了@SpringBootApplication注解,这个注解组合了@EnableAutoConfiguration注解,而它又组合了@Import注解,导入了AutoConfigrationImportSelector类,这个类实现了selectImports方法,这个方法经过层层调用,最终会读取META-INF目录下的后缀名为.imports的文件,在文件中读取到了全类名之后,会解析注册条件,也就是@Conditional及其衍生注解,把满足注册条件的Bean对象自动注入到IOC容器中。


相关文章
|
19天前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
|
2月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
739 0
|
4月前
|
JavaScript 前端开发 Java
制造业ERP源码,工厂ERP管理系统,前端框架:Vue,后端框架:SpringBoot
这是一套基于SpringBoot+Vue技术栈开发的ERP企业管理系统,采用Java语言与vscode工具。系统涵盖采购/销售、出入库、生产、品质管理等功能,整合客户与供应商数据,支持在线协同和业务全流程管控。同时提供主数据管理、权限控制、工作流审批、报表自定义及打印、在线报表开发和自定义表单功能,助力企业实现高效自动化管理,并通过UniAPP实现移动端支持,满足多场景应用需求。
448 1
|
5月前
|
前端开发 Java 关系型数据库
基于Java+Springboot+Vue开发的鲜花商城管理系统源码+运行
基于Java+Springboot+Vue开发的鲜花商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的鲜花商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。技术学习共同进步
421 7
|
2月前
|
前端开发 Java 数据库连接
SpringBoot参数校验底层原理和实操。深度历险、深度解析(图解+秒懂+史上最全)
SpringBoot参数校验底层原理和实操。深度历险、深度解析(图解+秒懂+史上最全)
SpringBoot参数校验底层原理和实操。深度历险、深度解析(图解+秒懂+史上最全)
|
2月前
|
XML 人工智能 IDE
Springboot整合SSMP报错分析
本文介绍了Springboot整合SSMP框架时常见的报错及解决方案,包括MyBatis-Plus版本不兼容导致的Lambda表达式条件构造器报错及表名不匹配问题。通过升级或降级MyBatis-Plus版本、使用@TableName注解或配置table-prefix属性,可有效解决上述问题,帮助开发者避免在整合SSMP时出现不必要的错误。
173 0
|
3月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
194 3
|
4月前
|
供应链 JavaScript BI
ERP系统源码,基于SpringBoot+Vue+ElementUI+UniAPP开发
这是一款专为小微企业打造的 SaaS ERP 管理系统,基于 SpringBoot+Vue+ElementUI+UniAPP 技术栈开发,帮助企业轻松上云。系统覆盖进销存、采购、销售、生产、财务、品质、OA 办公及 CRM 等核心功能,业务流程清晰且操作简便。支持二次开发与商用,提供自定义界面、审批流配置及灵活报表设计,助力企业高效管理与数字化转型。
451 2
ERP系统源码,基于SpringBoot+Vue+ElementUI+UniAPP开发
|
3月前
|
机器学习/深度学习 数据采集 人机交互
springboot+redis互联网医院智能导诊系统源码,基于医疗大模型、知识图谱、人机交互方式实现
智能导诊系统基于医疗大模型、知识图谱与人机交互技术,解决患者“知症不知病”“挂错号”等问题。通过多模态交互(语音、文字、图片等)收集病情信息,结合医学知识图谱和深度推理,实现精准的科室推荐和分级诊疗引导。系统支持基于规则模板和数据模型两种开发原理:前者依赖人工设定症状-科室规则,后者通过机器学习或深度学习分析问诊数据。其特点包括快速病情收集、智能病症关联推理、最佳就医推荐、分级导流以及与院内平台联动,提升患者就诊效率和服务体验。技术架构采用 SpringBoot+Redis+MyBatis Plus+MySQL+RocketMQ,确保高效稳定运行。
266 0
|
XML Java Maven
Spring Boot自动装配原理
昨天,有位在广州工作4年的小伙伴,在面试中被问到SpringBoot自动装配原理,当时,自我感觉比较好,他要的是30K,但是都没有拿到Offer。今天,我给大家分享一下我的理解。
283 0