首页 > 编程语言 >SpringBoot自动配置原理:底层源码分析

SpringBoot自动配置原理:底层源码分析

时间:2024-10-26 22:45:35浏览次数:9  
标签:SpringBoot spring 配置 boot annotationMetadata 源码 EnableAutoConfiguration 底层 confi

大家好,我是此林。

今天和大家分享面试常被问到的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

相关文章

  • 基于SpringBoot+Vue+uniapp的学生知识成果展示与交流的详细设计和实现(源码+lw+部署文
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • redis第152节答疑 redis源码分析String重要总结
    redis的string类型,如果数字大于10000,就不去共享整数中去取,然后就变成了embstr或者raw,为什么不是new一个redisobject,并且编码为int对于Redis的字符串类型(String),当字符串表示的是一个整数值时,Redis会根据具体情况选择不同的编码方式。对于数字大于10000的情况,Redis不会将其编......
  • jsp ssm 校园新闻管理系统 新闻发布系统 news 项目源码 web java
    一、项目简介本项目是一套基于SSM的校园新闻管理系统,主要针对计算机相关专业的和需要项目实战练习的Java学习者。包含:项目源码、数据库脚本、软件工具等。项目都经过严格调试,确保可以运行!二、技术实现​后端技术:Spring、SpringMVC、MyBatis前端技术:JSP、HTML、CSS、Ja......
  • jsp ssm 智能图书馆图书推荐系统 图书管理 项目源码 web java
    一、项目简介本项目是一套基于SSM的智能图书馆图书推荐系统,主要针对计算机相关专业的和需要项目实战练习的Java学习者。包含:项目源码、数据库脚本、软件工具等。项目都经过严格调试,确保可以运行!二、技术实现​后端技术:Spring、SpringMVC、MyBatis前端技术:JSP、HTML、C......
  • 基于Springboot无人驾驶车辆路径规划系统(源码+定制+开发)
    博主介绍:  ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W+粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台的优质作者。通过长期分享和实战指导,我致力于帮助更多学生......
  • SpringBoot编写WebApi~(1)idea创建项目并打包
    1.idea创建springboot项目,参考2、idea将springboot打包成jar,参考,对于新版idea默认使用gradle构建,则使用下面步骤build.gradle文件添加以下几行://打包配置bootJar{archiveBaseName.set('xxx-project')archiveVersion.set('0.0.1')archiveFileName.set('xxx-......
  • 基于Java+SpringBoot+Mysql实现的古诗词平台功能设计与实现二
    一、前言介绍:1.1项目摘要随着信息技术的迅猛发展和数字化时代的到来,传统文化与现代科技的融合已成为一种趋势。古诗词作为中华民族的文化瑰宝,具有深厚的历史底蕴和独特的艺术魅力。然而,在现代社会中,由于生活节奏的加快和信息获取方式的多样化,古诗词的传播和阅读面临着一定的挑......
  • 【含文档】基于ssm+jsp的大学生评优管理系统(含源码+数据库+lw)
    1.开发环境开发系统:Windows10/11架构模式:MVC/前后端分离JDK版本:JavaJDK1.8开发工具:IDEA数据库版本:mysql5.7或8.0数据库可视化工具:navicat服务器:apachetomcat主要技术:Java,Spring,SpringMvc,mybatis,mysql,vue2.视频演示地址3.功能系统定义了三个......
  • 【含文档】基于ssm+jsp的大学生互动交流网站(含源码+数据库+lw)
    1.开发环境开发系统:Windows10/11架构模式:MVC/前后端分离JDK版本:JavaJDK1.8开发工具:IDEA数据库版本:mysql5.7或8.0数据库可视化工具:navicat服务器:apachetomcat主要技术:Java,Spring,SpringMvc,mybatis,mysql,vue2.视频演示地址3.功能系统定义了两个......
  • 基于Java+SpringBoot+Mysql实现的古诗词平台功能设计与实现一
    一、前言介绍:1.1项目摘要随着信息技术的迅猛发展和数字化时代的到来,传统文化与现代科技的融合已成为一种趋势。古诗词作为中华民族的文化瑰宝,具有深厚的历史底蕴和独特的艺术魅力。然而,在现代社会中,由于生活节奏的加快和信息获取方式的多样化,古诗词的传播和阅读面临着一定的挑......