【SpringBoot】SpringBoot的自动配置源码解析

文章目录

  • 1. SpringBoot的自动配置概念
  • 2. SpringBoot自动配置的原理
  • 3. EnableAutoConfiguration
  • 4. 常用的Conditional注解

1. SpringBoot的自动配置概念

SpringBoot相对于SSM来说,主要的优点就是简化了配置,不再需要像SSM哪有写一堆的XML配置,这些XML配置在大项目上会成为一种累赘,使得后期项目难以维护。

SpringBoot的出现,使得开发者不再关注于配置,能够更加专注于业务的开发,这得益于SpringBoot的自动配置。


2. SpringBoot自动配置的原理

SpringBoot的自动配置的核心就在于SpringBoot启动类中的@SpringBootApplication注解上

@SpringBootApplication
@Slf4j
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);log.info("Project running .....");}}

这是一个复合注解,标识该类为SpringBoot的应用入口,里面包含了SpringBootConfigurationEnableAutoConfigurationComponentScan三个注解

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

三个注解的作用如下

  1. SpringBootConfiguration:这是SpringBoot框架中的一个特殊注解,属于@Configuration派生注解,它的作用是在应用启动时会被自动加载和处理,简化应用程序的配置过程,提供快速的启动配置.
  2. EnableAutoConfiguration:启用自动配置机制(自动配置的核心)
  3. ComponentScan:扫描启动类路径下的类,自动注册带有@Component以及其他相关注解的类到Spring容器中

3. 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 {};
}

EnableAutoConfiguration也是一个复合注解,其中AutoConfigurationPackage注解的作用是将当前类所在的包以及子包作为自动配置的包路径,以便让SpringBoot能够自动加载和处理这些组件的配置。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationPackages.Registrar.class})
public @interface AutoConfigurationPackage {String[] basePackages() default {};Class<?>[] basePackageClasses() default {};
}

AutoConfigurationPackage中有@Import({AutoConfigurationPackages.Registrar.class})

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

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

Registrar作为一个静态内部类,实现了ImportBeanDefinitionRegistrar接口,就可以被@Import注入到Spring容器中。

在这里类中,需要重点关注registerBeanDefinitions方法

image-20230713091837759

通过DEBUG发现,(String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0])的值为当前启动类所在的包名。

因此,可以得出结论,这个方法的主要作用是启动类所在的包下的所有组件注入到Spring容器中。

接着,再看@Import({AutoConfigurationImportSelector.class})

通过@ImportAutoConfigurationImportSelector选择器导入。

AutoConfigurationImportSelector需要重点关注getAutoConfigurationEntry方法

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {// 检查是否开始自动配置,如果返回false,则不开启,不需要导入任何配置类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);//使用配置类过滤器(ConfigurationClassFilter)对候选自动配置列表进行进一步筛选和过滤。configurations = this.getConfigurationClassFilter().filter(configurations);//触发自动配置导入事件,可以通知其他监听器关于自动配置的导入信息。this.fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}
}

这个方法的返回值是候选的配置类,经过处理和筛选后的自动配置类列表以及排除列表。

重点是关注如何获取自动配置类列表,这个需要关注getCandidateConfigurations方法

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {//使用 SpringFactoriesLoader 从 META-INF/spring.factories 文件中加载所有的工厂名称,并将它们存储在一个新的 ArrayList 中。//getSpringFactoriesLoaderFactoryClass() 返回用于加载自动配置的工厂类。List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));//使用 ImportCandidates 从指定的类路径加载 AutoConfiguration 类的子类或实现类,并将它们添加到候选配置列表中。//this.getBeanClassLoader() 返回当前线程的类加载器。ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor 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;
}

这个方法的主要作用就是利用SpringFactoriesLoader加载META-INF/spring.factories文件

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

接着查看loadSpringFactories,这里面就是获取需要配置的类的主要核心过程了。

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {//首先从缓存中获取给定类加载器(classLoader)对应的工厂配置信息。如果缓存中已存在,则直接返回结果。Map<String, List<String>> result = (Map)cache.get(classLoader);if (result != null) {return result;} else {//用于存储工厂配置信息。Map<String, List<String>> result = new HashMap();try {//使用给定的类加载器获取所有位于 META-INF/spring.factories 路径下的资源文件的 URL 枚举Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");//遍历每个资源文件的 URLwhile(urls.hasMoreElements()) {//获取下一个资源文件的 URLURL url = (URL)urls.nextElement();//将 URL 封装为 UrlResource 对象,以便进行读取。UrlResource resource = new UrlResource(url);//通过 PropertiesLoaderUtils 加载 UrlResource 对象所代表的资源文件,并将其作为属性对象 Properties 进行读取。Properties properties = PropertiesLoaderUtils.loadProperties(resource);Iterator var6 = properties.entrySet().iterator();//遍历每个属性条目。while(var6.hasNext()) {Map.Entry<?, ?> entry = (Map.Entry)var6.next();//提取工厂类型名称,并进行去除首尾空格的处理。String factoryTypeName = ((String)entry.getKey()).trim();//将工厂实现类名按逗号分隔,转换为字符串数组。String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());String[] var10 = factoryImplementationNames;int var11 = factoryImplementationNames.length;for(int var12 = 0; var12 < var11; ++var12) {String factoryImplementationName = var10[var12];//将工厂实现类名添加到对应的工厂类型键下的列表中。如果该键不存在,则创建一个新的空列表。((List)result.computeIfAbsent(factoryTypeName, (key) -> {return new ArrayList();})).add(factoryImplementationName.trim());}}}//对结果中的每个工厂类型及其对应的实现类列表,进行去重操作,并将列表转换为不可修改的集合,以确保唯一性result.replaceAll((factoryType, implementations) -> {return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));});//将结果存储到缓存中,以便下次直接获取cache.put(classLoader, result);return result;} catch (IOException var14) {throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);}}
}

也就是说这个方法通过加载和解析 META-INF/spring.factories 文件中的内容,将工厂类型和其对应的实现类名关联起来,并返回一个包含工厂配置信息的 Map 对象。它使用了缓存机制来避免重复加载相同的配置文件,提高了性能。这些工厂配置信息在 Spring Boot 中用于自动装配和初始化各种组件、功能和设置。

这个META-INF/spring.factories文件指什么呢?

在我们导入的每一个XXX-spring-boot-starter中,除了本身的jar包以外,还会有一个 xxx-spring-boot-autoConfigure,这个就是第三方依赖自己编写的自动配置类。我们现在就以 spring-boot-autocongigure 这个依赖来说。

image-20230713101119935

这些类就是自动配置的类了。就RedisAutoConfiguration而言

@AutoConfiguration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {public RedisAutoConfiguration() {}@Bean@ConditionalOnMissingBean(name = {"redisTemplate"})@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> template = new RedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}@Bean@ConditionalOnMissingBean@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {return new StringRedisTemplate(redisConnectionFactory);}
}

里面已经配置好了,Redis参数指定了RedisProperties配置文件,还有数据源配置文件。

我们只需要在yml文件配置好,后面的全交给SpringBoot框架自己配置即可。

SpringBoot自动配置流程.png


4. 常用的Conditional注解

@Conditional其实是spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效。

  1. @ConditionalOnClass : classpath中存在该类时起效
  2. @ConditionalOnMissingClass : classpath中不存在该类时起效
  3. @ConditionalOnBean : DI容器中存在该类型Bean时起效
  4. @ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
  5. @ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
  6. @ConditionalOnExpression : SpEL表达式结果为true时
  7. @ConditionalOnProperty: 参数设置或者值一致时起效
  8. @ConditionalOnResource : 指定的文件存在时起效
  9. @ConditionalOnJndi: 指定的JNDI存在时起效
  10. @ConditionalOnJava: 指定的Java版本存在时起效
  11. @ConditionalOnWebApplication : Web应用环境下起效
  12. @ConditionalOnNotWebApplication : 非Web应用环境下起效

参考:

  • 一文搞懂🔥SpringBoot自动配置原理 - 掘金 (juejin.cn)
  • SpringBoot自动配置原理详解 - 掘金 (juejin.cn)

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

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

相关文章

centos7.9修改ssh默认的端口号

多开几个ssh&#xff0c;防止配置文件错误&#xff0c;将自己关在服务器外面了 netstat -ntlp|grep ssh # ssh对应的端口号 修改sshd_config配置文件 /etc/ssh/sshd_config&#xff0c;重启sshd服务 #Port 22 Port 10011 # 端口号自己定义&#xff0c;不能超过65535&#xf…

Django系列所有漏洞复现vulhubCVE-2018-14574,CVE-2022-34265,CVE-2021-35042

文章目录 Django < 2.0.8 任意URL跳转漏洞&#xff08;CVE-2018-14574&#xff09;漏洞详情&#xff1a;复现&#xff1a; Django Trunc(kind) and Extract(lookup_name) SQL注入漏洞&#xff08;CVE-2022-34265&#xff09;漏洞详情&#xff1a;复现&#xff1a; Django Qu…

pyqt 实现计算器

1.由designer设计实现的计算器类 文件名为&#xff1a;untitled.py # -*- coding: utf-8 -*-# Form implementation generated from reading ui file untitled.ui # # Created by: PyQt5 UI code generator 5.15.9 # # WARNING: Any manual changes made to this file will b…

03-MySQL-基础篇-SQL之DDL语句

SQL之DDL语句 前言DDL数据库操作表操作查询操作数据类型案例修改删除 前言 本篇来学习下SQL中的DDL语句 DDL 全称Data Definition Language&#xff0c;数据定义语言&#xff0c;用来定义数据库对象(数据库&#xff0c;表&#xff0c;字段) 数据库操作 查询所有数据库 sh…

docker 发布镜像到阿里云容器镜像

1.登录阿里云 2.创建个人版实例&#xff0c;这里需要设置密码&#xff0c;一定要保存好 3.创建一个镜像仓库&#xff0c;个人实例最多创建3个 4.创建命名空间&#xff0c;个人实例最多300个 5.发布镜像到阿里云容器镜像 1、linux登录阿里云docker容器镜像 $ docker login --us…

考场作弊行为自动抓拍告警算法 yolov7

考场作弊行为自动抓拍告警系统通过yolov7python网络模型算法&#xff0c;考场作弊行为自动抓拍告警算法实时监测考场内所有考生的行为&#xff0c;对考生的行为进行自动抓拍&#xff0c;并分析判断是否存在作弊行为。YOLOv7 的发展方向与当前主流的实时目标检测器不同&#xff…

Clion 配置Mingw64的 c++开发环境

1、Mingw64的安装与环境变量的配置 Mingw64文件下载 Mingw64下载地址&#xff1a;https://sourceforge.net/projects/mingw-w64/files/ posix相比win32拥有C 11多线程特性&#xff0c;sjlj和seh对应异常处理特性&#xff0c;sjlj较为古老&#xff0c;所以选择seh 配置环境变…

Windows git bash输入vim报错,不能使用vim-plug插件管理器

Windows系统下的git bash在安装时自带了默认的vim&#xff0c;我自己也下了个gvim&#xff0c;并且配置了.vimrc&#xff0c;其中使用了vim-plug管理nerdtree这些插件。但是在bash中vim <file>时&#xff0c;就会蹦出来几行报错&#xff1a; 处理 /c/Users/<username…

训练自己的ChatGPT 语言模型(一).md

0x00 Background 为什么研究这个&#xff1f; ChatGPT在国内外都受到了广泛关注&#xff0c;很多高校、研究机构和企业都计划推出类似的模型。然而&#xff0c;ChatGPT并没有开源&#xff0c;且复现难度非常大&#xff0c;即使到现在&#xff0c;没有任何单位或企业能够完全复…