本文主要是介绍最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 一、自动配置概念
- 二、半自动配置(误~🙏🙏)
- 三、源码分析
- 1、验证DispatcherServlet的自动配置
- 2、源码分析入口@SpringBootApplication
- 3、@SpringBootConfiguration的@Configuration
- 4、@EnableAutoConfiguration的@AutoConfigurationPackage和@Import
- 5、AutoConfigurationImportSelector类
- 6、selectImports()方法
- 7、getAutoConfigurationEntry()方法
- 8、getCandidateConfigurations()方法
- 9、AutoConfiguration.imports配置文件
- 10、DispatcherServletAutoConfiguration类
- 11、@AutoConfiguration和@ConditionalOnClass
- 12、DispatcherServletConfiguration内部类
- 四、不同版本SpringBoot自动配置的区别
- 五、面试话术:说一说SpringBoot自动配置原理?
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容器中获取dispatcherServletSystem.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容器中。
这篇关于最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!