第05天 SpringBoot自动配置原理

每天学习一个知识点

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉
🍎个人主页:Leo的博客
💞当前专栏:每天一个知识点
✨特色专栏: MySQL学习
🥭本文内容:第05天 SpringBoot自动配置原理
🖥️个人小站 :个人博客,欢迎大家访问
📚个人知识库: 知识库,欢迎大家访问

首先看下一下自动配置的整个流程图
自动配置

自从有了 SpringBoot 之后,咋们就起飞了!各种零配置开箱即用,而我们之所以开发起来能够这么爽,自动配置的功劳少不了,今天我们就一起来讨论一下 SpringBoot 自动配置原理。

Spring Boot的自动配置是通过@EnableAutoConfiguration注解实现的。当该注解被标记在一个类上时,Spring Boot就会根据应用程序中所引入的依赖,自动配置应用程序所需的Bean、服务和其他组件。

1. Spring Boot自动配置的概念

Spring Boot自动配置是指,在Spring Boot中,通过一些规则来自动配置应用程序所需的Bean、服务和其他组件。这种自动配置的方式可以大大减少开发人员的工作量,因为他们不需要手动配置每个组件,而只需要在应用程序中引入所需的模块即可。

2. 逐步分析

2.1 @SpringBootApplication

一切的来自起源SpringBoot的启动类,我们发现main方法上面有个注解:@SpringBootApplication

package com.leo.demo02;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;/*** @author : Leo* @description : 启动类* @date 2023/8/10 11:22*/
@SpringBootApplication
@MapperScan("com.leo.demo02.mapper")
public class Application
{public static void main(String[] args){ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);}
}

@SpringBootApplication 标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的main方法来启动 SpringBoot 应用;它的本质是一个组合注解,我们点进去查看该类的元信息主要包含3个注解:

@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(里面就是@Configuration,标注当前类为配置类,其实只是做了一层封装改了个名字而已)
  • @EnableAutoConfiguration(开启自动配置)
  • @ComponentScan(包扫描)

注:@Inherited是一个标识,用来修饰注解,如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解

我们下面逐一分析这3个注解作用

2.2 @SpringBootConfiguration

我们继续点@SpringBootConfiguration进去查看源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;
}

@Configuration标注在某个类上,表示这是一个 springboot的配置类。可以向容器中注入组件。

2.3 @ComponentScan

  • @ComponentScan:配置用于 Configuration 类的组件扫描指令。
  • 提供与 Spring XML<context:component-scan> 元素并行的支持。
  • 可以 basePackageClassesbasePackages 来定义要扫描的特定包。 如果没有定义特定的包,将从声明该注解的类的包开始扫描

2.4 @EnableAutoConfiguration

  • @ComponentScan:配置用于 Configuration 类的组件扫描指令。
  • 提供与 Spring XML<context:component-scan> 元素并行的支持。
  • 可以 basePackageClassesbasePackages 来定义要扫描的特定包。 如果没有定义特定的包,将从声明该注解的类的包开始扫描

3. 自动配置

@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 {};
}

3.1 @AutoConfigurationPackage

  • 自动导入配置包
  • 点进去查看代码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {String[] basePackages() default {};Class<?>[] basePackageClasses() default {};
}

@Import 为spring的注解,导入一个配置文件,在springboot中为给容器导入一个组件,而导入的组件由 AutoConfigurationPackages.class的内部类Registrar.class 执行逻辑来决定是如何导入的。

@Import({Registrar.class})

点Registrar.class进去查看源码如下:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {Registrar() {}public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {//断点AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));}public Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));}
}

注:Registrar实现了ImportBeanDefinitionRegistrar类,就可以被注解@Import导入到spring容器里。

这个地方打断点

image-20230813203640545

运行可以查看到(String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])的值为com.leo.Applicaiton:当前启动类所在的包名

结论: @AutoConfigurationPackage 就是将主配置类(@SpringBootApplication 标注的类)所在的包下面所有的组件都扫描注册到 spring 容器中。

3.2 @Import({AutoConfigurationImportSelector.class})

作用:AutoConfigurationImportSelector开启自动配置类的导包的选择器,即是带入哪些类,有选择性的导入

点AutoConfigurationImportSelector.class进入查看源码,这个类中有两个方法见名知意:

  1. selectImports:选择需要导入的组件

    public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}
    }
  2. getAutoConfigurationEntry:根据导入的@Configuration类的AnnotationMetadata返回AutoConfigurationImportSelector.AutoConfigurationEntry

protected AutoConfigurationImportSelector.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 AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);}
}

this.getCandidateConfigurations(annotationMetadata, attributes)这里断点查看

image-20230813204333506

configurations数组长度为127,并且文件后缀名都为 **AutoConfiguration

结论: 这些都是候选的配置类,经过去重,去除需要的排除的依赖,最终的组件才是这个环境需要的所有组件。有了自动配置,就不需要我们自己手写配置的值了,配置类有默认值的。

我们继续往下看看是如何返回需要配置的组件的

1. getCandidateConfigurations(annotationMetadata, attributes)

方法如下:

image-20230813205426429

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;
}

这里有句断言: 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.”);

意思是:“在 META-INF/spring.factories 中没有找到自动配置类。如果您使用自定义包装,请确保该文件是正确的。“

结论: 即是要loadFactoryNames()方法要找到自动的配置类返回才不会报错。

2. getSpringFactoriesLoaderFactoryClass()

我们点进去发现:this.getSpringFactoriesLoaderFactoryClass()返回的是EnableAutoConfiguration.class这个注解。这个注解和@SpringBootApplication下标识注解是同一个注解。

protected Class<?> getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class;
}

结论:获取一个能加载自动配置类的类,即SpringBoot默认自动配置类为EnableAutoConfiguration

loadFactoryNames()
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {ClassLoader classLoaderToUse = classLoader;if (classLoaderToUse == null) {classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}String factoryTypeName = factoryType.getName();return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

image-20230813205719355

先是将 EnableAutoConfiguration.class 传给了 factoryType

然后String factoryTypeName = factoryType.getName();,所以factoryTypeName 值为 org.springframework.boot.autoconfigure.EnableAutoConfiguration

loadSpringFactories()

接着查看loadSpringFactories方法的作用

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {//断点查看Map<String, List<String>> result = cache.get(classLoader);if (result != null) {return result;}result = new HashMap<>();try {//注意这里:META-INF/spring.factoriesEnumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);while (urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {String factoryTypeName = ((String) entry.getKey()).trim();String[] factoryImplementationNames =StringUtils.commaDelimitedListToStringArray((String) entry.getValue());for (String factoryImplementationName : factoryImplementationNames) {//断点result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()).add(factoryImplementationName.trim());}}}// Replace all lists with unmodifiable lists containing unique elements//去重,断点查看result值result.replaceAll((factoryType, implementations) -> implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));cache.put(classLoader, result);}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}return result;
}

这里的 FACTORIES_RESOURCE_LOCATION 在上面有定义:META-INF/spring.factories

public final class SpringFactoriesLoader {/*** The location to look for factories.* <p>Can be present in multiple JAR files.*/public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

META-INF/spring.factories文件在哪里?? 在所有引入的java包的当前类路径下的META-INF/spring.factories文件都会被读取,如:

image-20230813210249346

该方法作用是加载所有依赖的路径META-INF/spring.factories文件,通过map结构保存,key为文件中定义的一些标识工厂类,value就是能自动配置的一些工厂实现的类,value用list保存并去重。

image-20230813210401399

在回看 loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());

因为 loadFactoryNames 方法携带过来的第一个参数为 EnableAutoConfiguration.class,所以 factoryType 值也为 EnableAutoConfiguration.class,那么 factoryTypeName 值为 EnableAutoConfiguration。拿到的值就是META-INF/spring.factories文件下的key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration的值

image-20230813211838324

getOrDefaultMap 集合中有这个 key 时,就使用这个 key值,如果没有就使用默认值空数组

结论:

  • loadSpringFactories()该方法就是从“META-INF/spring.factories”中加载给定类型的工厂实现的完全限定类名放到map中
  • loadFactoryNames()是根据SpringBoot的启动生命流程,当需要加载自动配置类时,就会传入org.springframework.boot.autoconfigure.EnableAutoConfiguration参数,从map中查找key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值,这些值通过反射加到容器中,之后的作用就是用它们来做自动配置,这就是Springboot自动配置开始的地方
  • 只有这些自动配置类进入到容器中以后,接下来这个自动配置类才开始进行启动
  • 当需要其他的配置时如监听相关配置:listenter,就传不同的参数,获取相关的listenter配置。

4. 自动配置流程细节梳理

导入starter-web:导入了web开发场景

  • 1、场景启动器导入了相关场景的所有依赖:starter-jsonstarter-tomcatspringmvc
  • 2、每个场景启动器都引入了一个spring-boot-starter,核心场景启动器。
  • 3、核心场景启动器引入了spring-boot-autoconfigure包。
  • 4、spring-boot-autoconfigure里面囊括了所有场景的所有配置。
  • 5、只要这个包下的所有类都能生效,那么相当于SpringBoot官方写好的整合功能就生效了。
  • 6、SpringBoot默认却扫描不到 spring-boot-autoconfigure下写好的所有配置类。(这些配置类给我们做了整合操作),默认只扫描主程序所在的包

2、主程序@SpringBootApplication

  • @SpringBootApplication由三个注解组成@SpringBootConfiguration@EnableAutoConfiguratio@ComponentScan

  • SpringBoot默认只能扫描自己主程序所在的包及其下面的子包,扫描不到 spring-boot-autoconfigure包中官方写好的配置类

  • **@EnableAutoConfiguration**:SpringBoot 开启自动配置的核心

    • 是由@Import(AutoConfigurationImportSelector.class)提供功能:批量给容器中导入组件。
    • SpringBoot启动会默认加载 142个配置类。
    • 142个配置类来自于spring-boot-autoconfigureMETA-INF/spring/**org.springframework.boot.autoconfigure.AutoConfiguration**.imports文件指定的
    • 项目启动的时候利用 @Import 批量导入组件机制把 autoconfigure 包下的142 xxxxAutoConfiguration类导入进来(自动配置类
    • 虽然导入了142个自动配置类
  • 按需生效:

    • 并不是这142个自动配置类都能生效
    • 每一个自动配置类,都有条件注解@ConditionalOnxxx,只有条件成立,才能生效

3、**xxxxAutoConfiguration**自动配置类

  • 给容器中使用@Bean 放一堆组件。
  • 每个自动配置类都可能有这个注解@EnableConfigurationProperties(**ServerProperties**.class),用来把配置文件中配的指定前缀的属性值封装到 xxxProperties属性类
  • 以Tomcat为例:把服务器的所有配置都是以server开头的。配置都封装到了属性类中。
  • 容器中放的所有组件的一些核心参数,都来自于**xxxProperties****xxxProperties**都是和配置文件绑定。
  • 只需要改配置文件的值,核心组件的底层参数都能修改

**4、**写业务,全程无需关心各种整合(底层这些整合写好了,而且也生效了)

核心流程总结:

1、导入starter,就会导入autoconfigure包。

2、autoconfigure 包里面 有一个文件 META-INF/spring/**org.springframework.boot.autoconfigure.AutoConfiguration**.imports,里面指定的所有启动要加载的自动配置类

3、@EnableAutoConfiguration 会自动的把上面文件里面写的所有自动配置类都导入进来。xxxAutoConfiguration 是有条件注解进行按需加载

4、xxxAutoConfiguration给容器中导入一堆组件,组件都是从 xxxProperties中提取属性值

5、xxxProperties又是和配置文件进行了绑定

**效果:**导入starter、修改配置文件,就能修改底层行为。

5. 常用的Conditional注解

  • 在加载自动配置类的时候,并不是将spring.factories的配置全部加载进来,而是通过@Conditional等注解的判断进行动态加载
  • @Conditional其实是spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效。
  • 常用的Conditional注解:
    • @ConditionalOnClass : classpath中存在该类时起效
    • @ConditionalOnMissingClass : classpath中不存在该类时起效
    • @ConditionalOnBean : DI容器中存在该类型Bean时起效
    • @ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
    • @ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
    • @ConditionalOnExpression : SpEL表达式结果为true时
    • @ConditionalOnProperty : 参数设置或者值一致时起效
    • @ConditionalOnResource : 指定的文件存在时起效
    • @ConditionalOnJndi : 指定的JNDI存在时起效
    • @ConditionalOnJava : 指定的Java版本存在时起效
    • @ConditionalOnWebApplication : Web应用环境下起效
    • @ConditionalOnNotWebApplication : 非Web应用环境下起效

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/65556.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Azure资源命名和标记决策指南

参考 azure创建虚拟机在虚拟机中选择编辑标签&#xff0c;并添加标记&#xff0c;点击应用 3.到主页中转到所有资源 4. 添加筛选器并应用 5.查看结果&#xff0c;筛选根据给服务器定义的标签筛选出结果。 参考链接: https://learn.microsoft.com/zh-cn/azure/cloud-adoption…

华为云Classroom赋能—TooKit助力开发者上云

对于资深程序员而言&#xff0c;IDE是必不可少的&#xff0c;它好比是剑客手中的宝剑&#xff0c;IDE帮助程序员更快更丝滑的去编程&#xff0c;同时插件就是这把剑上的各种Buff&#xff0c;为宝剑赋能&#xff0c;提供更好的升级打怪体验。 什么是Huawei Cloud Toolkit Huaw…

Docker部署rabbitmq遇到的问题 Stats in management UI are disabled on this node

1. Stats in management UI are disabled on this node #进入rabbitmq容器 docker exec -it {rabbitmq容器名称或者id} /bin/bash#进入容器后&#xff0c;cd到以下路径 cd /etc/rabbitmq/conf.d/#修改 management_agent.disable_metrics_collector false echo management_age…

前后端分离------后端创建笔记(04)前后端对接

本文章转载于【SpringBootVue】全网最简单但实用的前后端分离项目实战笔记 - 前端_大菜007的博客-CSDN博客 仅用于学习和讨论&#xff0c;如有侵权请联系 源码&#xff1a;https://gitee.com/green_vegetables/x-admin-project.git 素材&#xff1a;https://pan.baidu.com/s/…

9.利用matlab完成 泰勒级数展开 和 符号表达式傅里叶变换和反变换 (matlab程序)

1.简述 matlab之傅里叶变换和逆变换 首先生成一个方波&#xff08;或者其他组合波形&#xff09;&#xff0c;然后对这个信号做傅里叶变换&#xff0c;拆解到频域&#xff0c;可以看到这个信号是由哪些频率的信号叠加而来。 然后把频域信号&#xff0c;用傅里叶逆变换恢复到时…

现代控制理论step()函数使用方法,多输入多输出系统的阶跃响应图如何只输出一个输入对应输出的阶跃响应图(step(sys)如何单独显示一个子图)

多输入多输出系统的阶跃响应图 考虑以下二阶状态空间模型: A [-0.5572,-0.7814;0.7814,0]; B [1,-1;0,2]; C [1.9691,6.4493]; sys ss(A,B,C,0);这个模型有两个输入和一个输出&#xff0c;因此它有两个通道: 从第一个输入到输出&#xff0c;从第二个输入到输出。每个通道都…

Wlan——无线服务集和AP的基本概念以及AP的配置

目录 WLAN服务集的基本概念 AP的基本概念 AP的分类 AP模式的切换 胖&#xff08;FAT&#xff09;AP介绍 胖AP的工作模式 接入模式和路由模式的区别 胖AP的组网方式 瘦&#xff08;FIT&#xff09;AP介绍 瘦AP的工作模式 瘦AP的组网方式 胖AP和瘦AP的区别 AP的配置…

wps设置一键标题字体和大小

参考 wps设置一键标题字体和大小&#xff1a;https://www.kafan.cn/A/7v5le1op3g.html 统一一键设置

docker搭建LNMP

docker安装 略 下载镜像 nginx:最新版php-fpm:根据自己需求而定mysql:根据自己需求定 以下是我搭建LNMP使用的镜像版本 rootVM-12-16-ubuntu:/docker/lnmp/php/etc# docker images REPOSITORY TAG IMAGE ID CREATED SIZE mysql 8.0…

AI 绘画Stable Diffusion 研究(七) 一文读懂 Stable Diffusion 工作原理

大家好&#xff0c;我是风雨无阻。 本文适合人群&#xff1a; 想要了解AI绘图基本原理的朋友。 对Stable Diffusion AI绘图感兴趣的朋友。 本期内容&#xff1a; Stable Diffusion 能做什么 什么是扩散模型 扩散模型实现原理 Stable Diffusion 潜扩散模型 Stable Diffu…

【MySQL】InnoDB存储引擎详解

InnoDB引擎是MySQL5.5版本之后默认的存储引擎 逻辑存储结构 首先是表空间Tablespace&#xff08;ibd文件&#xff09;&#xff1a;一个mysql实力可以对应多个表空间&#xff0c;用于存储及记录&#xff0c;索引等数据 这些存储记录&#xff0c;索引等数据中是用段(Segment)来…

Gitlab CI/CD笔记-第二天-主机套接字进行构建并push镜像。

一、安装gitlab-runner 1.可以是linux也可以是docker的 2.本文说的是docker安装部署的。 二、直接上.gitlab-ci.yml stages: # List of stages for jobs, and their order of execution - build-image build-image-job: stage: build-image image: harbor.com:543/docke…