Spring Boot自动装配

前言

自动装配是 Spring Boot 最核心的功能之一,第三方可以基于这个特性非常方便的和 Spring 做整合,实现自己的 Starter,做到开箱即用。
Java 早期并不支持注解,所以那会儿 Spring 只能通过 xml 的形式来配置。早期项目里要引入一个功能模块,首先我们要引入 SDK,然后在 xml 里配置所需的 bean。随着引入的模块越来越多,开发者很快陷入 xml 的旋涡之中。
Spring 3.0 时代,官方开始提供@Import注解实现自动装配的能力,同时也伴随了一堆以@EnableXXX命名风格的注解,顾名思义加上这些注解就能启用对应的能力,这俩注解一般配合使用。
到了 Spring Boot 时代,自动装配再度迎来升级,它在@Import基础上增加了 SPI 的能力,而且还支持条件装配,使用上更加灵活。

理解自动装配

什么是自动装配???
跟自动装配相对立的就是手动装配,早期我们通过 xml 手动往容器里注册 bean 的方式就是手动装配。手动装配的缺点是:

  • 使用麻烦,需要维护一堆 xml
  • 使用门槛高,开发者需要知道配置细节

反之,自动装配就是开发者根据 Spring Boot 定制的规范编写AutoConfiguration类,Spring Boot 会自动加载这些配置类并把对应的 bean 注册到容器,这些 bean 是具备某种能力的,这样第三方就可以很轻松的把自己要提供的功能装进 Spring Boot。有了自动装配,开发者仅需加上少量注解或配置,甚至什么都不加(约定大于配置),就可以为项目引入一个功能模块。

装配的是什么???
从广义上理解,装配的是模块、是组件、是一个具体的功能。从狭义上理解,装配的其实是一个个具备某种能力的 bean。

自动装配是怎么实现的???
通过一个叫@EnableAutoConfiguration的注解往容器导入了一个叫AutoConfigurationImportSelector的类,它实现了ImportSelector接口,Spring 启动时会触发子类方法按照规则加载自动装配类。

设计实现

在使用 Spring Boot 开发时,我们一般会在启动类上加@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 {
    ......
}

它是一个复合注解,里面还集成了 Spring 提供的一些其它注解,从名字就能看出来,与自动装配有关的是@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也是一个复合注解,它最核心的功能就是往容器导入了AutoConfigurationImportSelector类。
image.png
AutoConfigurationImportSelector 实现了各种 Aware 接口,具备 BeanFactory、BeanClassLoader 等感知能力。最重要的是它实现了 DeferredImportSelector 接口,DeferredImportSelector 又继承自 ImportSelector。
ImportSelector 接口用来向容器注册批量导入配置类,子类重写selectImports()返回要导入的类的全限定名数组:

public interface ImportSelector {

	String[] selectImports(AnnotationMetadata importingClassMetadata);

	@Nullable
	default Predicate<String> getExclusionFilter() {
		return null;
	}
}

子接口 DeferredImportSelector 的区别是二者导入的时机不同,ImportSelector 会在@Configuration Bean 处理前调用,DeferredImportSelector 会等处理完所有的@Configuration Bean 之后再调用。
所以,Spring 启动时会先触发AutoConfigurationImportSelector.AutoConfigurationGroup#process收集要导入的类,再触发selectImports()返回导入项的迭代器。

@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
            () -> String.format("Only %s implementations are supported, got %s",
                    AutoConfigurationImportSelector.class.getSimpleName(),
                    deferredImportSelector.getClass().getName()));
    // 获取要导入的自动配置类
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
            .getAutoConfigurationEntry(annotationMetadata);
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}

核心是getAutoConfigurationEntry(),它会按照 Spring Boot 的规范加载自动装配类,去重后再移除掉需要被排除的类,接着触发一个 AutoConfigurationImportListener 监听事件,最后返回收集到的类。

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    // 可以通过配置spring.boot.enableautoconfiguration=false来禁用自动装配
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 加载候选配置类
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 去重 转HashSet
    configurations = removeDuplicates(configurations);
    // 移除掉要排除的类
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    // 触发AutoConfigurationImportListener监听事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

Spring Boot 会按照什么规则加载配置类呢?方法是getCandidateConfigurations(),它会加载候选的配置类,有两套加载规则:

  • 读取 ClassPath 下META-INF/spring.factories文件里以org.springframework.boot.autoconfigure.EnableAutoConfiguration为 Key 配置的类
  • 读取 ClassPath 下META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里配置的所有类

第一套规则更多的是给第三方提供的口子,第二套规则是 Spring Boot 导入内部配置类的口子。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    /**
     * 读取ClassPath下META-INF/spring.factories文件里
     * 被org.springframework.boot.autoconfigure.EnableAutoConfiguration标注的类
     */
    List<String> configurations = new ArrayList<>(
            SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
    /**
     * 读取ClassPath下META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
     * 文件里配置的类都要导入
     */
    ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
    return configurations;
}

读取文件加载配置类用到了 Spring 提供的 SpringFactoriesLoader 类,代码不复杂,这里就不赘述了。
我们重点看一下,Spring Boot 内部都提供了哪些配置类,文件路径在spring-boot-autoconfigure/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,官方默认提供了 144 个自动装配类,这里贴几个示例。

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration
......

这些配置类基本从名字就可以看出它们的功能,例如:AopAutoConfiguration 用来配置切面编程相关的 bean;RabbitAutoConfiguration 用来配置 RabbitMQ 相关的 bean。
自动配置类本身会作为 bean 注册到容器,除此之外还可以通过@Bean注解方法的形式注册 bean。如果要提供的功能比较复杂,不想所有的代码都耦合在一个类里面,还可以在配置类上继续加@Import注解引入另一个配置类。

条件配置
官方一股脑提供了 144 个自动配置类,我们不一定都需要啊,所以条件配置诞生了。
条件配置的意思是,可以给自动配置类加上一些前置条件,只有这些条件都满足了,配置类才会生效。条件配置伴随的是一堆以@ConditionalXXX命名风格的注解,这里举几个常用的:

  • @ConditionalOnBean:容器存在满足条件的 bean 才生效
  • @ConditionalOnClass:容器存在满足条件的 Class 才生效
  • @ConditionalOnMissingBean:容器不存在满足条件的 bean 才生效
  • @ConditionalOnMissingClass:容器不存在满足条件的 Class 才生效
  • @ConditionalOnWebApplication:必须是指定类型的 Web 应用环境才生效

有了条件配置,即使官方一股脑提供了一堆自动配置类,很多也都是不会生效的,要想生效我们得引入相关的依赖和配置。
以 RabbitAutoConfiguration 为例,它的生效条件是存在 RabbitTemplate.class、Channel.class,如果我们没有引入相关依赖,这些类肯定是不存在的,配置类自然也就不会生效了。

@AutoConfiguration
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import({ RabbitAnnotationDrivenConfiguration.class, RabbitStreamConfiguration.class })
public class RabbitAutoConfiguration {
    
}

尾巴

所谓的自动装配,就是 Spring Boot 程序在启动时会去扫描 ClassPath 下的META-INF/spring.factories文件,自动把 AutoConfiguration 类注册到容器,免去了开发者需要自己配置 bean 的麻烦过程。Spring Boot 一股脑提供了 144 个自动配置类,绝大多数开发者并不需要,所以提供了基于条件的自动配置,只有前置条件都满足了,配置类才会生效。基于自动装配和约定大于配置的设计理念,开发者仅需编写少量配置甚至不写任何配置就可以方便的引入一个功能模块。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[Vulnhub靶机] DriftingBlues: 6

[Vulnhub靶机] DriftingBlues: 6靶机渗透思路及方法&#xff08;个人分享&#xff09; 靶机下载地址&#xff1a; https://download.vulnhub.com/driftingblues/driftingblues6_vh.ova 靶机地址&#xff1a;192.168.67.25 攻击机地址&#xff1a;192.168.67.3 一、信息收集 …

分布式限流和本地限流那些事?

分布式限流和本地限流的目的是一样的&#xff0c;当然我建议技术人在自己的服务中优先考虑本地限流&#xff0c;那样对于自己的API的影响会小一点。 限流这种技术&#xff0c;在没有触发限流的阈值的时候&#xff0c;是不会有什么大的问题的&#xff0c;当时一旦触发阈值&…

在树莓派OS Bookworm中如何安装Python包

树莓派OS "Bookworm"版本&#xff0c;用于树莓派5上&#xff0c;更改了安装Python模块的方法。 关键要点&#xff1a; 1&#xff09;树莓派OS Bookworm需要在一个虚拟环境中安装Python包来防止与Python的系统版本发生冲突。 2&#xff09;你可以使用apt包管理器来搜…

如何在群辉NAS使用Docker搭建容器魔方并实现无公网ip远程访问

文章目录 1. 拉取容器魔方镜像2. 运行容器魔方3. 本地访问容器魔方4. 群辉安装Cpolar5. 配置容器魔方远程地址6. 远程访问测试7. 固定公网地址 本文主要介绍如何在群辉7.2版本中使用Docker安装容器魔方&#xff0c;并结合Cpolar内网穿透工具实现远程访问本地网心云容器魔方界面…

商品源数据如何采集,您知道吗?

如今&#xff0c;电子商务已经渗透到了人们生活的方方面面。2020年新冠肺炎突如其来&#xff0c;打乱了人们正常的生产生活秩序&#xff0c;给经济发展带来了极大的影响。抗击疫情过程中&#xff0c;为避免人员接触和聚集&#xff0c;以“无接触配送”为营销卖点的电子商务迅速…

【数据结构】7大排序最详细

0.前言 接下来进入排序&#xff0c;我们知道在c语言阶段可能就学习过了像冒泡排序&#xff0c;选择排序这种比较简单的排序&#xff0c;那么接下来我们就会学习到更加高级的排序算法。但高级代表着难度的提升&#xff0c;但不用担心&#xff0c;博主会细细来谈&#xff0c;慢慢…

使用Rider C# Dll工程和Unity工程互相调用、断点方法

总体流程 创建C# Dll工程&#xff0c;生成C#工程Dll 创建Unity工程 Unity调用C#工程的代码 C#工程调用Unity工程的代码 断点方法 创建C# Dll工程&#xff0c;生成C#工程Dll 创建工程 选这个&#xff0c;注意UnityEngineDll这个选项&#xff0c;要选你目标unity版本的Dll…

【【深入浅出了解静态时钟分析和时钟约束】】

深入浅出了解静态时钟分析和时钟约束 时序分析是什么&#xff1f; 我们提出一些特定的时序要求&#xff08;或者说是添加特定的时序约束&#xff09;&#xff0c;使用特定的时序模型&#xff0c;针对特定的电路进行分析。分析的最终结果是要求系统时序满足我们提出的要求。 这…

MySQL的三种存储引擎 InnoDB、MyISAM、Memory

InnoDB 1). 介绍 InnoDB是一种兼顾高可靠性和高性能的通用存储引擎&#xff0c;在 MySQL 5.5 之后&#xff0c;InnoDB是默认的MySQL 存储引擎。 2). 特点 DML操作遵循ACID模型&#xff0c;支持事务&#xff1b; 行级锁&#xff0c;提高并发访问性能&#xff1b; 支持外键F…

云渲染是什么?云渲染有什么用?云渲染对本地的渲染的影响有哪些?

如果你曾经在深夜加班&#xff0c;眼睁睁看着电脑显卡冒着烟、渲染器哭着喊着把你的CPU和GPU当成磨盘来使用&#xff0c;那么你可能会对云渲染心生向往。毕竟&#xff0c;谁不想在享用最新游戏画面和电影级效果的同时&#xff0c;不用忍受电脑发热、卡顿等问题呢&#xff1f; 云…

What does “grep -i“ do?

grep&#xff08; Global Regular Expression Print &#xff09;根据 给定的正则表达式 搜索文本&#xff0c;并将匹配的行打印出来 grep -i 表示查找的过程中忽略大小写 在已安装的rpm包里&#xff0c;查询是否有tree相关的包 rpm -qa | grep -i "tree"在文件中搜…

在Vivado下利用Tcl实现IP的高效管理

在Vivado下利用Tcl实现IP的高效管理https://cloud.tencent.com/developer/article/1736645 在Vivado下&#xff0c;有两种方式管理IP。一种是创建FPGA工程之后&#xff0c;在当前工程中选中IP Catalog&#xff0c;生成所需IP&#xff0c;这时相应的IP会被自动添加到当前工程中&…

一键去背景怎么去?使用这些智能抠图软件即可已经去背景

作为一名设计师&#xff0c;我们的工作不仅仅是追逐创意的火花&#xff0c;为品牌和产品打造独特的形象。在美好的表象之下&#xff0c;有一项常常让我们苦恼的“必修课”——抠图。抠图是设计过程中不可或缺的一环&#xff0c;它要求我们对图像进行精细的处理&#xff0c;以确…

2019年认证杯SPSSPRO杯数学建模D题(第一阶段)5G时代引发的道路规划革命全过程文档及程序

2019年认证杯SPSSPRO杯数学建模 5G下十字路口车辆通行效率的讨论和建模 D题 5G时代引发的道路规划革命 原题再现&#xff1a; 忙着回家或上班的司机们都知道交通堵塞既浪费时间又浪费燃料&#xff0c;甚至有的时候会带来情绪上的巨大影响&#xff0c;引发一系列的交通问题。…

使用脚本把springboot程序部署到k8s上

一般我们部署写4个文件就行了 首先分别写 Dockerfile1package.sh2build.shdocker_push.sh Dockerfile # 基于jdk1.8 FROM openjdk:8-jdk-alpine RUN echo "Asia/Shanghai" > /etc/timezone RUN mkdir /app WORKDIR /appCOPY ./monitor-flink-1.0.jar /appEXPO…

WPF 布局

了解 WPF中所有布局如下&#xff0c;我们一一尝试实现&#xff0c;本文档主要以图形化的形式展示每个布局的功能。 布局&#xff1a; Border、 BulletDecorator、 Canvas、 DockPanel、 Expander、 Grid、 GridView、 GridSplitter、 GroupBox、 Panel、 ResizeGrip、 Separat…

【软件测试作业_TPshop商城】农业工程学院-测试需求分析与测试计划+自动化+性能+测试用例+报告软件缺陷+测试计划+单元测试+系统测试

1测试需求分析与测试计划 1.1 被测系统简介 1.2测试需求分析 1.2.1单元测试层面的测试需求分析 1.2.2系统测试层面的测试需求分析 1.3测试计划 1.31测试范围与任务 1.3.2 测试环境 1.3.3测试进度安排 测试用例的设计2 2.1单元测试层面的测试用例设计 2.2系统测试层面的测试用例…

Redis:原理速成+项目实战——Redis实战10(Redis消息队列实现异步秒杀)

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;Redis&#xff1a;原理速成项目实战——Redis实战9&#xff08;秒杀优化&#xff09; &#x1f4da;订阅专栏&#xff1a;Redis&…

zookerper入门

zookerper介绍 ZooKeeper 是一个开源的分布式协调框架,主要用来解决分布式集群中应用系统的一致性问题. ZooKeeper本质上是一个分布式的小文件存储系统&#xff08;Zookeeper文件系统监听机制&#xff09;.提供基于类似于文件系统的目录树方式的数据存储&#xff0c;并且可以…

PFA试剂瓶——实验室存储运输化学试剂样品容器

PFA是一种高性能的塑料材料。它是一种热塑性塑料&#xff0c;由全氟化&#xff0c;聚合物制成&#xff0c;具有高度的化学稳定性性。由于其优异的性能&#xff0c;PFA被广泛应用于多个领域&#xff0c;尤其是作为存储和运输各种化学试剂的容器&#xff0c;耐受-200℃至260C的温…