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

news2024/11/22 18:46:33

文章目录

  • 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");
			//遍历每个资源文件的 URL
            while(urls.hasMoreElements()) {
                //获取下一个资源文件的 URL
                URL 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.coloradmin.cn/o/749859.html

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

相关文章

SQLSERVER的truncate和delete有区别吗?

一&#xff1a;背景 1. 讲故事 在面试中我相信有很多朋友会被问到 truncate 和 delete 有什么区别 &#xff0c;这是一个很有意思的话题&#xff0c;本篇我就试着来回答一下&#xff0c;如果下次大家遇到这类问题&#xff0c;我的答案应该可以帮你成功度过吧。 二&#xff1…

全网最细,Pytest自动化框架fixture和conftest.py实战详解(细致)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 fixture说明 fix…

你一定不知道的自动化测试的9大规则

目录 前言 应该去做的事情 雇用合适的人 在寻找正确的测试自动化工具方面花点时间 轻装上阵 让开发人员参与到自动化过程中来 在ci/cd上投资时间 不应该做的事情 不要因为一个工具被追捧就选择它 不要试图将一切都自动化 不要太早实现自动化 永远不要用自动化来取代…

C语言-报错集锦-02-munmap_chunk(): invalid pointer: 0x0000000001d2e150 ***

一、报错信息 [2023-7]--[ Debug ]--Destroy DqlResult Struct OK [2023-7]--[ Debug ]--Destroy Moia Base Job : OK [2023-7]--[ Debug ]--Destroy Moia Base Job : OK [2023-7]--[ Debug ]--Destroy Moia Base Job : OK [2023-7]--[ Debug ]--Destroy Mo…

Redis学习(二)线程安全、分布式锁、消息队列

文章目录 优惠券秒杀全局ID生成器优惠券秒杀下单超卖问题一人一单 分布式锁基于Redis的setnx指令实现分布式锁解决锁误删问题基于Lua脚本实现多条指令原子性Redis调用Lua脚本Java中使用Lua脚本 RedissonRedisson快速入门Redisson可重入锁原理Redisson的锁重试和Watchdog机制Red…

【经济调度】基于多目标宇宙优化算法优化人工神经网络环境经济调度研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

适配各类大模型应用!手把手教你选择 Zilliz Cloud 实例类型

作为大模型时代备受关注的细分赛道&#xff0c;向量数据库可以不仅为大模型提供存储和向量检索的功能&#xff0c;还能适配各种 AI 应用场景&#xff0c;例如聊天机器人、内容审核、增强 LLM 知识库等。 不过&#xff0c;对于向量数据库的开发者而言&#xff0c;成本是绕不开的…

Spring学习笔记---SpringBoot快速入门

Spring学习笔记---SpringBoot快速入门 Spring学习笔记---SpringBoot1 SpringBoot简介1.1 SpringBoot快速入门1.1.1 开发步骤1.1.1.1 创建新模块1.1.1.2 创建 Controller1.1.1.3 启动服务器1.1.1.4 进行测试 1.1.2 对比1.1.3 官网构建工程1.1.3.1 进入SpringBoot官网1.1.3.2 选择…

(二)springboot实战——springboot基于多端内容协商适配实现json、xml、yaml等格式数据统一返回

前言 在实际应用开发场景中&#xff0c;我们有需求实现多端内容请求的适配&#xff0c;例如某些客户端需要返回json数据&#xff0c;有些客户端需要返回xml数据&#xff0c;有些客户端要返回yaml数据&#xff0c;这个时候就需要服务端做内容返回的适配&#xff0c;如果按照提供…

搭建vsto的clickonce一键发布IIS环境FTP

要在 Windows 上启用 IIS&#xff08;Internet Information Services&#xff09;&#xff0c;可以按照以下步骤进行操作&#xff1a;1. 打开“控制面板”&#xff1a;点击 Windows 开始菜单&#xff0c;然后在搜索栏中输入“控制面板”&#xff0c;并选择相应的结果。2. 打开“…

Maven 项目构建生命周期

Maven 项目构建生命周期 一句话: Maven 构建生命周期描述的是一次构建过程经历了多少个事件 生命周期的3 大阶段 clean 清理工作 default 核心工作&#xff0c;例如编译&#xff0c;测试&#xff0c;打包&#xff0c;部署等 site 产生报告&#xff0c;发布站点等 生命周期…

zsh自定义命令行提示符

环境&#xff1a; oh-my-zsh 插件 效果&#xff1a; 本来的样子&#xff1a;感觉元素很多&#xff0c;比较挤占地方 现在的样子&#xff1a;简洁了很多 步骤&#xff1a; 打开主题的配置文件&#xff08;我的主题是agnoster &#xff09; cd /Users/你的家目录/.oh-my-zsh/the…

Windows操纵kafka

这里写目录标题 启动kafk创建一个测试主题查看所有主题查看first详细信息修改分区数(分区数只能增加 不能减少)删除主题生产者生产数据消费命令 启动kafk 安装目录下 .\bin\windows\kafka-server-start.bat .\config\server.properties创建一个测试主题 安装目录下 .\bin\wi…

【Unity2D像素风格小游戏】期末考考完,和搭档一个月从零开始的Unity速成作品!

游戏实况视频 六月十八号&#xff0c;期末考完后&#xff0c;大佬搭档和我开始自学unity&#xff0c;并在七月一号正式开始一个unity2D像素小游戏的制作&#xff0c;这是一段很有意义&#xff0c;很有收获的日子。 这个项目由搭档提出&#xff0c;另一位超级大佬进行前期指导…

【分布式系统案例课】计数服务之存储设计

存什么&#xff1f; 两种存储内容&#xff0c;各有优劣 综合&#xff1a;耽搁时间和聚合数据都分别进行存储&#xff0c;可以得到两者的好处。 数据库选型 SQL数据库客户端嵌入代理 因为B站的数量级&#xff0c;单个数据库肯定是扛不住的。然后为了满足扩展性需求&#xf…

Python入门级语法详解以及实战练习

来源&#xff1a;投稿 作者&#xff1a;起酥松松 编辑&#xff1a;学姐 python基本语法元素 变量命名原则&#xff1a; 首字符不能是数字&#xff1b; 中间不能有空格&#xff1b; 不能是python保留字&#xff1b; 字母 &#xff0c;数字 &#xff0c;汉字 &#xff0c;下划…

【学会动态规划】三步问题(2)

目录 动态规划怎么学&#xff1f; 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后&#xff1a; 动态规划怎么学&#xff1f; 学习一个算法没有捷径&#xff0c;更何况是学习动态规划&#xff0c; 跟我…

骨传导耳机好不好,盘点五款当下最流行的骨传导耳机

其实早在几年前&#xff0c;骨传导耳机就已经出现在大众视野了&#xff0c;由于使用的人数不多&#xff0c;所以一直不温不火的&#xff0c;最近这几年&#xff0c;骨传导耳机的热度才开始兴起&#xff0c;其最大的特点就是&#xff0c;不用入耳佩戴&#xff0c;不用担心运动过…

HOT60-单词搜索

leetcode原题链接&#xff1a;单词搜索 题目描述 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#…

分体式骨传导,为敏感耳朵打造的舒适体验,南卡OE上手

骨传导蓝牙耳机这两年很受运动玩家的欢迎&#xff0c;不过标准的骨传导耳机用起来并不算方便&#xff0c;最近我看到南卡新推出一种分体式骨传导耳机&#xff0c;叫南卡OE&#xff0c;和普通的骨传导耳机有很大不同。上周我也入手了一款&#xff0c;这几天试了试&#xff0c;感…