大家好,我是此林。
今天和大家分享面试常被问到的SpringBoot自动配置原理。SpringBoot 之所以因为其 “开箱即用” 的特性备受开发者欢迎,其中一个重要的原因就是自动配置。今天我们就来扒一扒,SpringBoot 的自动配置到底是如何运作的,底层如何帮助我们省去繁琐的配置。
在SpringBoot 没有问世之前,我们的开发流程可能是这样的:
比如我们正在开发一个 Web 应用程序,需要使用 Spring MVC 和 Thymeleaf 模板引擎。
在传统的 Spring 应用程序中,我们需要手动配置 Spring MVC 和 Thymeleaf。
第一步,在 pom.xml 中添加 Spring MVC 和 Thymeleaf 的依赖。
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.10</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>
第二步,自定义配置类(也可以用XML等方法配置)
@Configuration
@EnableWebMvc
public class SpringMVCConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.thymeleaf();
}
@Bean
public TemplateResolver templateResolver() {
TemplateResolver templateResolver = new ServletContextTemplateResolver();
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML5");
return templateResolver;
}
@Bean
public SpringTemplateEngine templateEngine(TemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
@Bean
public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
}
configureViewResolvers() 方法:注册 Thymeleaf 视图解析器,告诉 Spring MVC 使用 Thymeleaf 来解析视图。
templateResolver() 方法:定义了一个 TemplateResolver Bean,用于解析 Thymeleaf 模板。
setPrefix:设置模板文件的前缀路径,例如 /WEB-INF/templates/。
setSuffix:设置模板文件的后缀,例如 .html。
setTemplateMode:设置模板模式,这里设置为 HTML5,表示模板文件是 HTML5 格式的。
可以看到,很多参数和配置每次都要我们手动去做,十分不方便。而SpringBoot 则使用默认的配置帮我们自动装配类,减少了大量繁琐重复的细节,这也就是为什么SpringBoot “约定大于配置”的原因了。
如果我们使用SpringBoot来自动配置类,我们只需要在pom.xml中导入相关starter依赖就可以了。无需手动配置。
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
那么,SpringBoot 在底层是如何实现这一过程的呢?
原因就是SpringBoot 在启动类上加了@SpringBootApplication 注解。
@SpringBootApplication
public class MyspringbootApplication {
public static void main(String[] args) {
SpringApplication.run(MyspringbootApplication.class, args);
}
}
@SpringBootApplication 点进去
@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 {
...
}
发现有个@EnableAutoConfiguration 注解。
@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 {};
}
发现它使用@Import 导入了 AutoConfigurationImportSelector.class
AutoConfigurationImportSelector.class 继续点进去。
发现一段核心代码:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
它的返回值是一个字符串数组,其实就是自动配置好的类。
返回值类似这样:{"com.example.AutoConfig1", "com.example.AutoConfig2", ......},即类的全类名。
自动配置好的类的获取通过这一行代码获取。
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
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);
}
}
1. 通过 this.isEnabled(annotationMetadata) 判断是否启用了自动配置,如果没有启用则直接返回 EMPTY_ENTRY (空条目)。
2. 通过 this.getAttributes(annotationMetadata) 获取注解的属性。
3. 通过 this.getCandidateConfigurations(annotationMetadata, attributes) 获取候选配置类列表。
4. 通过 this.removeDuplicates(configurations) 去除重复的配置类。
5. 通过 this.getExclusions(annotationMetadata, attributes) 获取需要排除的配置类集合。
6. 通过 this.checkExcludedClasses(configurations, exclusions) 检查并移除被排除的配置类。
7. 通过 this.getConfigurationClassFilter().filter(configurations) 进一步过滤配置类。
8. 通过 this.fireAutoConfigurationImportEvents(configurations, exclusions) 触发自动配置导入事件。
其实说白了,就是先通过getCandidateConfigurations() 获取预选配置类列表,然后经过层层过滤剔除,最后触发自动配置导入事件。
getCandidateConfigurations() 获取预选配置类列表,这个很重要。
点进 getCandidateConfigurations()
点进 loadFacrotyNames()
点进 loadSpringFactories()
这里就发现了 META-INF/spring.factories,该方法内部就是读取了项目的classpath路径下 META-INF/spring.factories 文件中的所配置的类的全类名。
这个就是 META-INF/spring.factories,可以看见,该文件是一个属性文件,每行定义一个键值对。键和值都是全限定名。
现在,我们再回到getCandidateConfigurations() 方法里,
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
我们目前已经知道了这个方法里通过 SpringFactoriesLoader.loadFactoryNames() 来读取 META-INF/spring.factories 里需要自动配置类的全限定名数组。
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
事实上,这里传入了参数 this.getSpringFactoriesLoaderFactoryClass()
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
也就是传入了 EnableAutoConfiguration.class,那么在SpringFactoriesLoader.loadFactoryNames() 来读取 META-INF/spring.factories 里只会读取键名为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的值。
如何验证只会读取键名为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的值呢?
我们再回到 getAutoConfigurationEntry() 方法,运行时打断点。
META-INF/spring.factories 的 org.springframework.boot.autoconfigure.EnableAutoConfiguration 键对应的值有127个,这里有130个的原因是因为项目里引入了
mybatis-spring-boot-starter 和 pagehelper-spring-boot-starter,并且在yml文件里手动配置了Bean,而这几个配置类是默认没有的。
这里的深度解析:
事实上,你会发现,AutoConfigurationImportSelector 类实现了 DeferredImportSelector 接口,
DeferredImportSelector 是 ImportSelector 的子接口,用于延迟配置类的导入。
这使得自动配置类的加载时机在手动配置类被解析之后、Bean 定义被加载之前。这样做的目的是优先解析手动配置类,因为有时候我们可能并不想使用默认的自动配置类,所以手动配置重写。
所以这也就是上文为什么配置类数组多了3个的原因。
当然,最后获取到的配置类数组会进行去重、排除、过滤获取最终需要的配置类。可以发现,配置类数组已经从130个减少到了35个。
简单总结下,
1. 当 Spring Boot 启动时,会扫描类路径下的 META-INF/spring.factories 文件。
2. 找到 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的值(即自动配置类的全限定名)。
3. 将这些类加载到应用上下文中,并根据条件注解(@ConditionalOnXXX)决定是否应用这些配置到Spring容器中。
更详细点的总结就是,
1. @SpringBootApplication 注解中有 @EnableAutoConfiguration 注解,@EnableAutoConfiguration 注解里使用了@Import({AutoConfigurationImportSelector.class}),
2. AutoConfigurationImportSelector 使用 getAutoConfigurationEntry() 方法
3. getAutoConfigurationEntry() 先通过 getCandidateConfigurations() 方法获取 META-INF/spring.factories 文件里键名为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的所有自动配置类的列表。
4. 在经过一系列条件过滤后,按需选择把装配好的Bean加载到Spring 容器中。
为什么不一次性全部加载到容器中?
如果Spring没有添加任何的附加条件,此时这些配置类中所定义的bean都会被导入到Spring容器中,这样
非常消耗内存
,因此在Spring中提供了很多的条件注解,通过这些条件注解控制某一个配置是否生效。
今天的分享就到这里啦,有什么疑惑的小伙伴欢迎在评论区讨论!
老朋友此林,带你看不一样的世界!
标签:SpringBoot,spring,配置,boot,annotationMetadata,源码,EnableAutoConfiguration,底层,confi From: https://blog.csdn.net/2401_82540083/article/details/143255919