高频面试题:谈谈你对 Spring Boot 自动装配机制的理解

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 谈谈你对 Spring Boot 自动装配机制的理解什么是 Spring Boot 自动装配?@SpringBootApplication@SpringBootConfiguration@EnableAutoConfigurationAutoConfigurationImportSelectorSpring Boot 中的 SPI 机制Spring Boot 作为现在 Java 微服务开发中的中流砥柱,其开箱即用、免去 xml 配置等特点,高效便捷的使 Java 程序员进行业务开发。每次面试 Spring Boot 相关技术点时,其自动装配原理也是重点关注对象。下面我将结合源码针对 S

谈谈你对 Spring Boot 自动装配机制的理解

  • 什么是 Spring Boot 自动装配?
  • @SpringBootApplication@SpringBootConfiguration@EnableAutoConfigurationAutoConfigurationImportSelector
  • Spring Boot 中的 SPI 机制

Spring Boot 作为现在 Java 微服务开发中的中流砥柱,其开箱即用、免去 xml 配置等特点,高效便捷的使 Java 程序员进行业务开发。每次面试 Spring Boot 相关技术点时,其自动装配原理也是重点关注对象。下面我将结合源码针对 Spring Boot 实现自动配置做一个详细的介绍。

阅读完本文你能知道:

  • Spring Boot 诞生背景
  • 什么是 Spring Boot 自动装配?
  • Spring Boot 启动时的自动配置的原理知识
  • Spring Boot 启动时的自动配置的流程
  • 对于 Spring Boot 一些常用注解的了解

一步一步 debug 从浅到深。

注意:本文的 Spring Boot 版本为 2.6.3。

Spring Boot 诞生背景

使用过 Spring 的小伙伴,一定有被 XML 配置统治的恐惧。

我们回顾下原来搭建一个 Spring MVC 的 helloword 的 web 项目( xml 配置的)我们是不是要在 pom 中导入各种依赖,然后各个依赖有可能还会存在版本冲突需要各种排除。当你历尽千辛万苦的把依赖解决了,然后还需要编写 web.xmlspringmvc.xml 配置文件等。

我们只想写个 helloword 项目而已,确把一大把的时间都花在了配置文件和 jar 包的依赖上面。大大的影响了我们开发的效率,以及加大了 web 开发的难度。

为了简化这复杂的配置、以及各个版本的冲突依赖关系,Spring Boot 就应运而生。

我们现在通过 idea 创建一个 Spring Boot 项目只要分分钟就解决了,你不需要关心各种配置(基本实现零配置)。让你真正的实现了开箱即用。它的出现不仅可以让你把更多的时间都花在你的业务逻辑开发上,而且还大大的降低了 web 开发的门槛。所以 Spring Boot 还是比较善解人意,知道开发人员的痛点在哪。

为什么 Spring Boot 使用起来这么酸爽呢? 这得益于其自动装配。自动装配可以说是 Spring Boot 的核心,那究竟什么是自动装配呢?

什么是 Spring Boot 自动装配?

Spring Boot 有一个全局配置文件: application.propertiesapplication.yml 。在这个全局文件里面可以配置各种各样的参数比如:你想改个端口啦 server.port 或者想调整下日志的级别都可以配置。

更多其他可以配置的属性可以参照 官网 。

这么多属性,这些属性在项目是怎么起作用的呢?

Spring Boot 项目看下来啥配置也没有( application.propertiesapplication.yml 除外),既然从配置上面找不到突破口,那么我们就只能从启动类上面找入口了。启动类也就一个光秃秃的一个 main 方法,类上面仅有一个注解 SpringBootApplication 这个注解是 Spring Boot 项目必不可少的注解。那么自动配置原理一定和这个注解有着千丝万缕的联系!

我们下面来一起看看这个注解吧。

探究自动装配原理

@SpringBootApplication

@SpringBootApplication 标注在某个类上说明这个类是 Spring Boot 的主配置类, Spring Boot 就应该运行这个类的 main 方法来启动 Spring Boot 应用;它的本质是一个 组合注解 ,我们点进去查看。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
  @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

这里最上面四个注解的话没啥好说的,基本上自己实现过自定义注解的话,都知道分别是什么意思。关键就在于后面三个注解:

  • @SpringBootConfiguration
  • @ComponentScan
  • @EnableAutoConfiguration

我们逐个分析下。

@SpringBootConfiguration

这个注解我们点进去就可以发现,它实际上就是一个 @Configuration 注解,这个注解大家应该很熟悉了,加上这个注解就是为了让当前类作为一个配置类交由 Spring 的 IOC 容器进行管理,因为 Spring Boot 本质上还是 Spring,所以原属于 Spring 的注解 @Configuration 在 Spring Boot 中也可以直接应用。

由此可见, @SpringBootConfiguration 注解的作用与 @Configuration 注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过 @SpringBootConfiguration 是被 Spring Boot 进行了重新封装命名而已。

@ComponentScan

这个注解也很熟悉,用于定义 Spring 的扫描路径,等价于在 xml 文件中配置 <context:component-scan> ,假如不配置扫描路径,那么 Spring 就会默认扫描当前类所在的包及其子包中的所有标注了 @Component@Service@Controller 等注解的类。

@EnableAutoConfiguration

这个注解才是实现自动装配的关键,点进去之后发现,它是一个由 @AutoConfigurationPackage@Import 注解组成的复合注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

看起来很多注解,实际上关键在 @Import 注解,它会加载
AutoConfigurationImportSelector
类,然后就会触发这个类的 selectImports() 方法。根据返回的 String 数组(配置类的 Class 的名称)加载配置类。

我们重点看下
AutoConfigurationImportSelector

AutoConfigurationImportSelector

AutoConfigurationImportSelector中的selectImport是自动装配的核心实现,它主要是读取META-INF/spring.factories文件,经过去重、过滤,返回需要装配的配置类集合。

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
  if (!isEnabled(annotationMetadata)) {
    return NO_IMPORTS;
  }
  AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
  return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

我们点进 getAutoConfigurationEntry() 方法:

  • getAttributes 获取 @EnableAutoConfiguration 中的 excludeexcludeName 等。
  • getCandidateConfigurations 获取所有自动装配的配置类,也就是读取 spring.factories 文件,后面会再次说明。
  • removeDuplicates 去除重复的配置项。
  • getExclusions 根据 @EnableAutoConfiguration 中的 excludeexcludeName 移除不需要的配置类。
  • fireAutoConfigurationImportEvents 广播事件。
  • 最后根据多次过滤、判重返回配置类合集。

现在我们结合 getAutoConfigurationEntry() 的源码来详细分析一下:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
  if (!isEnabled(annotationMetadata)) {
    return EMPTY_ENTRY;
  }
  AnnotationAttributes attributes = getAttributes(annotationMetadata);
  List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  configurations = removeDuplicates(configurations);
  Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  checkExcludedClasses(configurations, exclusions);
  configurations.removeAll(exclusions);
  configurations = getConfigurationClassFilter().filter(configurations);
  fireAutoConfigurationImportEvents(configurations, exclusions);
  return new AutoConfigurationEntry(configurations, exclusions);
}

第 1 步:判断自动装配开关是否打开。

默认
spring.boot.enableautoconfiguration=true
,可在 application.propertiesapplication.yml 中设置。

网络异常,图片无法展示
|

第 2 步 :

用于获取 EnableAutoConfiguration 注解中的 excludeexcludeName

网络异常,图片无法展示
|

第 3 步:

获取需要自动装配的所有配置类,读取 META-INF/spring.factories

网络异常,图片无法展示
|

我们点进
getCandidateConfigurations()
方法:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
      getBeanClassLoader());
  Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
      + "are using a custom packaging, make sure that file is correct.");
  return configurations;
}

获取候选配置了使用了 Spring Framework 自定义的 SPI 机制,使用 SpringFactoriesLoader#loadFactoryNames 加载了类路径下
/META-INF/spring.factories
文件中的配置类,里面是以 key/value 形式存储,其中一个 key 是 EnableAutoConfiguration 类的全类名,而它的 value 是一个以 AutoConfiguration 结尾的类名的列表。以 spring-boot-autoconfigure 模块为例,其 spring.factories 内容如下。

网络异常,图片无法展示
|

不光是这个依赖下的 META-INF/spring.factories 被读取到,所有 Spring Boot Starter 下的 META-INF/spring.factories 都会被读取到。

如果,我们自定义一个 Spring Boot Starter,就需要创建 META-INF/spring.factories 文件。

第 4 步 :

到这里可能面试官会问你:“ spring.factories 中这么多配置,每次启动都要全部加载么?”。

很明显,这是不现实的。我们 debug 到后面你会发现, configurations 的值变小了。

网络异常,图片无法展示
|

虽然 133 个全场景配置项的自动配置启动的时候默认全部加载。但实际经过后续处理后只剩下 25 个配置项真正加载进来。很明显,Spring Boot 只会加载实际你要用到的场景中的配置类。这是如何做到的了?

网络异常,图片无法展示
|

按需加载

这里我们分析剩下的 25 个自动配置类,观察到每一个自动配置类都有着 @Conditional 或者其派生条件注解。

  • @ConditionalOnBean:当容器里有指定 Bean 的条件下
  • @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
  • @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
  • @ConditionalOnClass:当类路径下有指定类的条件下
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下
  • @ConditionalOnProperty:指定的属性是否有指定的值
  • @ConditionalOnResource:类路径是否有指定的值
  • @ConditionalOnExpression:基于 SpEL 表达式作为判断条件
  • @ConditionalOnJava:基于 Java 版本作为判断条件
  • @ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置
  • @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
  • @ConditionalOnWebApplication:当前项目是 Web 项 目的条件下
@Configuration(
    proxyBeanMethods = false
)
// 检查是否有该类才会进行加载
@ConditionalOnClass({
 RedisOperations.class})
// 绑定默认配置信息
@EnableConfigurationProperties({
 RedisProperties.class})
@Import({
 LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }
   ...
}

所以当 classpath 下存在某一个 Class 时,某个配置才会生效。

上面所有的注解都在做一件事:注册 bean 到 Spring 容器。他们通过不同的条件不同的方式来完成:

  • @SpringBootConfiguration 通过与 @Bean 结合完成 Bean 的 JavaConfig 配置;
  • @ComponentScan 通过范围扫描的方式,扫描特定注解注释的类,将其注册到 Spring 容器;
  • @EnableAutoConfiguration 通过 spring.factories 的配置,并结合 @Condition 条件,完成bean的注册;
  • @Import 通过导入的方式,将指定的 class 注册解析到 Spring 容器;

我们在这里画张图把 @SpringBootApplication 注解包含的几个注解分别解释一下。

网络异常,图片无法展示
|

我们现在提到自动装配的时候,一般会和 Spring Boot 联系在一起。但是,实际上 Spring Framework 早就实现了这个功能。Spring Boot 只是在其基础上,通过 SPI 的方式,做了进一步优化。

SPI 全称为 Service Provider Interface,是一种服务发现机制。为了被第三方实现或扩展的 API,它可以用于实现框架扩展或组件替换

SPI 机制本质是 将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载文件中的实现类,这样可以在运行时,动态为接口替换实现类 。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。

用生活中的例子说就是,你买了一台小米的手机。但是你用的充电器并不一定非要是小米充电器,你可以拿其他厂商的充电器来进行充电,只要满足协议、端口等要求,那么就是可以充电的。这也是一种热拔插的思想,并不是固定死的。

换成代码来说也是一样的,我定义了一个接口,但是不想固定死具体的实现类,因为那样如果要更换实现类就要改动源代码,这往往是不合适的。

那么我也可以定义一个规范,在之后需要更换实现类或增加其他实现类时,遵守这个规范,我也可以动态的去发现这些实现类。

一个接口可以有很多实现,比如数据库驱动,有 oracle,mysql,postgress 等等,他们都遵循JDBC 规范,为了解耦,我们可以抽象出一个高层的 Driver 接口,让各个数据库服务商去实现各自的驱动,在使用的时候我们可以选择加载具体的实现方式,这时候我们就可以使用 SPI 这种技术。

JDK 中的 SPI

讲到 JDK 中的 SPI ,我们不得不说 java.util.ServiceLoader 这个类,我们先跑起来。

1、创建一个接口,Message

public interface Message{
    void send()
}

2、在 resources 资源目录下创建 META-INF/services 文件夹

3、在 services 文件夹中创建文件,以接口全名命名

4、创建接口实现类

public class SmsMessage implements Message{
    public void send(){
        System.out.println("send sms message");
    }
}
public class EmailMessage implements Message{
    public void send(){
        System.out.println("send email message");
    }
}

5、测试

public class TestServiceLoader{
    public static void main(String[] args){
        ServiceLoader<Message> messages = ServiceLoader.load(Message.class);
        for(Message msg : messages){
            msg.send();
        }
    }
}

打印结果:

send email message
send sms message

简单一点来说,就是在 META-INF/services 下面定义个文件,然后通过一个特殊的类加载器,启动的时候加载你定义文件中的类,这样就能扩展原有框架的功能。

Spring Boot 中的 SPI 机制

在 Spring Boot 的自动装配过程中,最终会加载 META-INF/spring.factories 文件,Spring Boot 是通过 SpringFactoriesLoader#loadFactoryNames 方法加载的。

Spring Boot 定义了一套接口规范,这套规范规定:Spring Boot 在启动时会扫描外部引用 jar 包中的 META-INF/spring.factories 文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 Spring Boot 定义的标准,就能将自己的功能装置进 Spring Boot。

本文我们主要介绍了 Spring Boot 自动装配原理。

简单来说, Spring Boot 通过 @EnableAutoConfiguration 开启自动装配,通过 SpringFactoriesLoader 最终加载 META-INF/spring.factories 中的自动配置类实现自动装配,自动配置类其实就是通过 @Conditional 按需加载的配置类,想要其生效必须引入 spring-boot-starter-xxx 包实现起步依赖。

好了,关于 Spring Boot 自动装配机制就聊这么多,感兴趣的同学也可以手撸一个自定义 Starter 来加深一遍印象。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
77 2
|
3天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
3天前
|
Java 数据库连接 Maven
最新版 | SpringBoot3如何自定义starter(面试常考)
在Spring Boot中,starter是一种特殊的依赖,帮助开发人员快速引入和配置特定功能模块。自定义starter可以封装一组特定功能的依赖和配置,简化项目中的功能引入。其主要优点包括模块化、简化配置、提高代码复用性和实现特定功能。常见的应用场景有短信发送模块、AOP日志切面、分布式ID生成等。通过创建autoconfigure和starter两个Maven工程,并编写自动配置类及必要的配置文件,可以实现一个自定义starter。最后在测试项目中验证其有效性。这种方式使开发者能够更便捷地管理和维护代码,提升开发效率。
最新版 | SpringBoot3如何自定义starter(面试常考)
|
10天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
55 14
|
26天前
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
44 4
|
1月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
63 8
|
2月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
|
2月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
61 1
|
2月前
|
监控 架构师 Java
从蚂蚁金服面试题窥探STW机制
在Java虚拟机(JVM)中,垃圾回收(GC)是一个至关重要的机制,它负责自动管理内存的分配和释放。然而,垃圾回收过程并非没有代价,其中最为显著的一个影响就是STW(Stop-The-World)机制。STW机制是指在垃圾回收过程中,JVM会暂停所有应用线程的执行,以确保垃圾回收器能够正确地遍历和回收对象。这一机制虽然保证了垃圾回收的安全性和准确性,但也可能对应用程序的性能产生显著影响。
42 2
|
2月前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?