这篇文章主要学习SpringBoot的自动配置实现原理(经典面试题目👀️ ,一言不合就问到了😕 )
SpringBoot Starter
说自动配置实现之前,得先说一下 Spring Boot Starter
, Spring Boot Starter
是一系列依赖关系的集合,因为它的存在,项目的依赖之间的关系对我们来说变的更加简单了。说白了就是SpringBoot帮我们直接统一了依赖的引入以及版本管理,使我们开发人员不需要把太多工作放在依赖管理上面,减少依赖冲突问题的出现。比如我们web项目开发会引入如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
点进去
里面引入了spring web相关的依赖,再点进 spring-boot-starter
里面是spring的核心依赖以及springboot自动配置的核心依赖,这就是SpringBoot的启动器了
SpringBoot 自动配置实现原理
弄清楚几个关键注解以及SpringBoot启动流程中做了什么事儿,也就明白了自动配置的实现原理了
@SpringBootApplication 注解
@SpringBootApplication
是 Spring Boot 框架中的核心注解之一,用于标识我们的主配置类,通常是项目的入口类。它整合了多个关键的功能,大大简化了 Spring Boot 应用的开发和配置过程 ,@SpringBootApplication
注解实际上包含了以下三个注解的功能:
@Configuration
:标注该类是一个配置类,允许使用@Bean
注解来定义和注册 Bean。@EnableAutoConfiguration
:启用 Spring Boot 的自动配置机制。Spring Boot 会根据项目的依赖和配置来决定应用程序的配置,从而减少了手动配置的工作量。@ComponentScan
:启用组件扫描,自动扫描并加载符合条件的组件,包括@Controller
、@Service
、@Repository
等。
点进 @SpringBootApplication
注解可以看到
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(...)
public @interface SpringBootApplication {
//.....
}
@SpringBootConfiguration
注解点开就是 @Configuration
注解,声明当前类可以作为配置类
@EnableAutoConfiguration 注解
前面说了,这个注解就是用来开启自动配置的功能,先看看定义
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
@AutoConfigurationPackage 注解
@AutoConfigurationPackage
注解主要作用是指定 Spring Boot 扫描的包。默认情况下,它会扫描启动类(即带有 @SpringBootApplication
注解的类)所在的包及其子包下的所有组件。通过这个注解,Spring Boot 能够自动发现并加载这些包下的所有 Spring 组件,如使用 @Service
、@Controller
、@Repository
等注解的类,并将它们注册到 Spring 容器中。
也许你会疑惑 @AutoConfigurationPackage
和 @ComponentScan
区别是什么(我也疑惑😄 ),请看解释:
- @ComponentScan:这个注解用于自动扫描并加载Spring应用程序上下文中的组件,比如@Component、@Service、@Repository、@Controller等。它告诉Spring在哪里查找Spring管理的组件
- @AutoConfigurationPackage:这个注解用于指定基础包,用于扫描Spring Boot组件以进行自动配置。通常与@SpringBootApplication一起使用,用于定义主应用程序类所在的包
总之,@ComponentScan用于扫描和加载应用程序上下文中的组件,而@AutoConfigurationPackage用于指定用于自动配置的组件的基础包。(我理解直白一点就是 一个扫面咱们开发中使用的注解,一个是扫描SpringBoot框架本身组件,功能有重叠,场景不一样)
@Import 注解
@Import
注解的作用是导入配置类或者 Bean 到当前类中(关于这个注解的学习准备单独整理一篇笔记,这里里知道它的作用就行),前面我们看到 @EnableAutoConfiguration
注解实现中使用了 @Import({AutoConfigurationImportSelector.class})
导入了 AutoConfigurationImportSelector
类,自动装配核心功能的实现就是通过 AutoConfigurationImportSelector
类,我们重点关注这个类
AutoConfigurationImportSelector 类
先看一下 AutoConfigurationImportSelector
类的继承关系
//实现 DeferredImportSelector 接口
public class AutoConfigurationImportSelector implements DeferredImportSelector,
BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
//......
}
//DeferredImportSelector接口继承ImportSelector接口
public interface DeferredImportSelector extends ImportSelector {
}
//ImportSelector接口定义
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
AutoConfigurationImportSelector
类实现了 ImportSelector
接口,也就实现了这个接口中的 selectImports
方法,该方法主要用于**获取所有符合条件的类的全限定类名,这些类需要被加载到 IOC 容器中,**看该方法的实现
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
if (!this.isEnabled(annotationMetadata))
这一步骤很好理解,是否开启自动配置,如果没有就返回一个 NO_IMPORTS
空字符串类型的数组,看一下 isEnabled
方法的实现
protected boolean isEnabled(AnnotationMetadata metadata) {
return this.getClass() == AutoConfigurationImportSelector.class ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
}
getAutoConfigurationEntry()方法
重点关注 getAutoConfigurationEntry()
方法,该方法的主要作用是用来加载自动配置类。源码如下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
执行流程
现在我们启动SpringBoot项目,通过调试分析一下启动流程
第一步:
判断自动装配开关是否打开。默认 spring.boot.enableautoconfiguration=true
,可在 application.properties
或 application.yml
中设置
第二步:
执行 AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
获取 EnableAutoConfiguration
注解中的 exclude
和 excludeName
第三步:
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件内容(我测试用的是SpringBoot 3.x 以上的版本,跟2.x不一样,2.x版本应该是 META-INF/spring.factories
),获取自动配置类的信息
第四步:
问题来了,前面我们加载了自动配置类的信息,足足152条之多,而我们依赖只导入了一个web应用的启动依赖,难不成每次要加载这么多配置吗?当然不是,SpringBoot会根据条件进行筛选,满足自动配置条件的类才会被保留,看截图:
this.removeDuplicates(configurations)
这一步很简单,就是把集合去重。this.getExclusions(annotationMetadata, attributes);
记得吗,我们之前获取的@EnableAutoConfiguration
注解中的两个属性exclude
、excludeName
,就是用在这里,顾名思义里面是我们排除不进行自动装配的类。this.getConfigurationClassFilter().filter(configurations);
将不满足@ConditionalOnXXX
条件的配置类,全部过滤掉
@ConditionalOnXXX
注解就存在于配置类上面
这些类随便点开进去
可以看到类上存在各种条件注解,只有当所有条件全部满足时类才会被创建生效,比如:@ConditionalOnClass
注解意思就是当类路径下有指定类的条件下,类才会被加载,那么这一步由谁决定呢?没错是我们👀️ ,当我们导入对应启动器依赖时,指定类它不就存在了吗,那我们自动配置类不就该生效了吗,完美!🎉️
以下是各种条件注解对应的含义:
@ConditionalOnBean
:当容器里有指定 Bean 的条件下@ConditionalOnMissingBean
:当容器里没有指定 Bean 的情况下@ConditionalOnSingleCandidate
:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean@ConditionalOnClass
:当类路径下有指定类的条件下@ConditionalOnMissingClass
:当类路径下没有指定类的条件下@ConditionalOnProperty
:指定的属性是否有指定的值@ConditionalOnResource
:类路径是否有指定的值@ConditionalOnExpression
:基于 SpEL 表达式作为判断条件@ConditionalOnJava
:基于 Java 版本作为判断条件@ConditionalOnJndi
:在 JNDI 存在的条件下差在指定的位置@ConditionalOnNotWebApplication
:当前项目不是 Web 项目的条件下@ConditionalOnWebApplication
:当前项目是 Web 项 目的条件下
通过前面分析的流程,最终SpringBoot通过 AutoConfigurationImportSelector
类的 selectImports
拿到了需要进行自动配置的类信息,最后就拿去创建各种Bean了。
总结
本篇文章中我们学习了SpringBoot自动配置功能的原理,通过源码跟踪直观看到了自动配置的加载流程。
有待补充的点有:
- SpringBoot怎么读取条件注解
- 如何实现一个自定义的starter
- 能不能自定义条件注解
- so on
菜鸡一枚,说的哪里有问题的话烦请大佬指正❤️