当前位置: 首页 > news >正文

SpringBoot 自动装配原理

零、前言

Spring简直是java企业级应用开发人员的春天,我们可以通过Spring提供的ioc容器,避免硬编码带来的程序过度耦合。但是,启动一个Spring应用程序也绝非易事,他需要大量且繁琐的xml配置,开发人员压根不能全身心的投入到业务中去。
因此,SpringBoot诞生了,虽然本质上还是属于Spring,但是SpringBoot的优势在于以下两个特点:

  1. 约定大于配置

SpringBoot定义了项目的基本骨架,例如各个环境的配置文件统一放到resource中,使用active来启用其中一个。配置文件默认为application.properties,或者yaml、yml都可以。

  1. 自动装配

以前在Spring使用到某个组件的时候,需要在xml中对配置好各个属性,之后被Spring扫描后注入进容器。
而有了SpringBoot后,我们仅仅需要引入一个starter,就可以直接使用该组件,如此方便、快捷,得益于自动装配机制。

一、定义

什么是SpringBoot的自动装配?

SpringBoot 自动装配,英文是 Auto-Configuration,是指在Spring Boot启动时,通过一系列机制和注解,将第三方组件或配置类加载到Spring容器中,并生成相应的Bean对象供使用。这一过程极大地简化了开发工作,提高了效率,为 SpringBoot 框架的 “开箱即用”提供了基础支撑;
具体来说,SpringBoot的自动装配主要依赖以下几个关键点

  1. 注解:SpringBoot提供了多个注解来实现自动装配,例如@SpringBootApplication@EnableAutoConfiguration@ComponentScan等。这些注解帮助SpringBoot识别并加载需要自动配置的类。
  2. 条件化配置:SpringBoot利用条件化配置(@Conditional注解)来决定是否进行某些配置。例如,如果类路径下存在某个特定的jar包,则自动配置相应的功能。
  3. 元数据文件:SpringBoot会在启动时扫描META-INF/spring.factories 文件,从中获取需要进行自动装配的类,并执行这些类中的配置操作。
  4. 场景启动器:SpringBoot还引入了场景启动器(如WebMvcAutoConfiguration),根据项目中添加的依赖自动配置相应的功能。
  5. 约定大于配置:SpringBoot遵循“约定大于配置”的原则,通过默认配置减少开发者手动编写配置的必要性。例如,在没有明确配置的情况下,SpringBoot会自动配置数据库连接、视图解析器等。
  6. SPI机制:自动装配与Spring的SPI(Service Provider Interface)机制相似,都是通过反射机制动态加载和实例化组件。更多SPI讲解:深入理解 Java SPI - 概念、原理、应用

二、启动流程

SpringBoot启动流程的简化版代码

public static void run(Class<?> primaryClass) {//1.创建一个 ApplicationContext 实例,即我们常说的IoC容器ApplicationContext context = createApplicationContext();// 2.将主类(primaryClass)注册到IoC容器中(简单但很重要的一步)loadSourceCLass(context, primaryClass);// 3.递归加载并处理所有的配置类processConfigurationClasses(context);// 4.实例化所有的单例Bean(Singleton Bean)instantiateSingletonBeans(context);// 5.如果时web应用,则启动web服务器(如Tomcat)startWebServer(context);
}

图解:
启动流程.png
详解第三步核心流程:
加载配置类流程.png
图解:
加载配置流程图.png

三、原理剖析

一切都从注解 @SpringBootApplication 说起:

@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

@SpringBootApplication

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

其中**@Target**、** @Retention**、@Documented与**@Inherited**是元注解,即对注解的注解;
还包含了@SpringBootConfiguration与@EnableAutoConfiguration
以下注解都将省略这些元注解

@SpringBootConfiguration

@Configuration
public @interface SpringBootConfiguration {
}@Component
public @interface Configuration {@AliasFor(annotation = Component.class)String value() default "";
}

可以看到,@SpringBootConfiguration 注解本质上是一个 @Configuration 注解,表明该类是一个配置类。
@Configuration 又被 @Component 注解修饰,代表任何加了**@Configuration** 注解的配置类,都会被注入进Spring容器中。

@EnableAutoConfiguration

该注解开启了自动配置的功能

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {...}

本身又包含了以下两个注解
@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)

@AutoConfigurationPackage

以前我们直接使用Spring的时候,需要在xml中的 context:component-scan 中定义好base-package,那么 Spring 在启动的时候,就会扫描该包下及其子包下被@Controller、@Service与@Component标注的类,并将这些类注入到容器中。
@AutoConfigurationPackage 则会将被注解标注的类,即主配置类,将主配置类所在的包当作base-package,而不用我们自己去手动配置了。
这也就是为什么我们需要将主配置类放在项目的最外层目录中的原因。
那么容器是怎么知道主配置当前所在的包呢?
我们注意到,@AutoConfigurationPackage 中使用到了@Import注解
@Import注解会直接向容器中注入指定的组件
引入了AutoConfigurationPackages类中内部类 Registrar

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {register(registry, new PackageImport(metadata).getPackageName());}@Overridepublic Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new PackageImport(metadata));}
}

而getName将会返回主配置类所在的包路径;这样,容器就知道了主配置类所在的包,之后就会扫描该包及其子包。

@Import(AutoConfigurationImportSelector.class)

该注解又引入了AutoConfigurationImportSelector类,而AutoConfigurationImportSelector中有一个可以获取候选配置的方法,即getCandidateConfigurations

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,AnnotationAttributes attributes) {List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), 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;
}protected Class<?> getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class;
}

其中核心方法SpringFactoriesLoader.loadFactoryNames,第一个参数是EnableAutoConfiguration.class

loadFactoryNames方法
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {String factoryClassName = factoryClass.getName();return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap<String, String> result = cache.get(classLoader);if (result != null) {return result;}try {Enumeration<URL> urls = (classLoader != null ?classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));result = new LinkedMultiValueMap<>();while (urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {String factoryClassName = ((String) entry.getKey()).trim();for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {result.add(factoryClassName, factoryName.trim());}}}cache.put(classLoader, result);return result;}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}
}

可以看得出,loadSpringFactorie方法,会从META-INF/spring.factories文件中读取配置,将其封装为Properties对象,将每个key作为返回的map的key,将key对应的配置集合作为该map的value。
而loadFactoryNames则是取出key为EnableAutoConfiguration.class的配置集合
我们查看META-INF/spring.factories的内容(完整路径:\org\springframework\boot\spring-boot\2.7.17\spring-boot-2.7.17.jar!\META-INF\spring.factories)
spring.factories.png
可以看到,EnableAutoConfiguration对应的value,则是我们在开发中经常用到的组件,比如Rabbit、Elasticsearch与Redis等中间件。
到这里,我们可以知道getCandidateConfigurations方法会从META-INF/spring.factories中获取各个组件的自动配置类的全限定名。
图解
selector.png
总结一下
总结一下.png

参考

SpringBoot的自动装配原理、自定义starter与spi机制,一网打尽
目前讲的最透彻的SpringBoot自动配置

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 五,搭建环境:辅助功能
  • 亚信安慧入选2024信创产业白皮书,AntDB荣获数据库卓越品牌
  • 云计算实训24——python基本环境搭建、变量和数据类型、数据集合、py脚本
  • Win11+docker+gpu+vscode+pytorch配置
  • LeetCode——3131.找出与数组相加的整数I
  • Base64在线解码工具
  • three.js 模型高亮效果实现说明(结合react)
  • Java基础之文件字节流
  • OpenCV 读取 MP4 视频
  • MySQL:触发器(Trigger)
  • C# 枚举 扩展方法
  • 【乐吾乐大屏可视化组态编辑器】数据绑定
  • Mac 连接 Synology NAS【Finder】
  • 【C语言篇】自定义类型:联合体和枚举详细介绍
  • 【django升级】django从2.2.6版本升级到3.2.25
  • IE9 : DOM Exception: INVALID_CHARACTER_ERR (5)
  • [分享]iOS开发-关于在xcode中引用文件夹右边出现问号的解决办法
  • ABAP的include关键字,Java的import, C的include和C4C ABSL 的import比较
  • Eureka 2.0 开源流产,真的对你影响很大吗?
  • Git同步原始仓库到Fork仓库中
  • Java知识点总结(JavaIO-打印流)
  • Laravel 菜鸟晋级之路
  • markdown编辑器简评
  • unity如何实现一个固定宽度的orthagraphic相机
  • 测试如何在敏捷团队中工作?
  • 时间复杂度与空间复杂度分析
  • 微信开源mars源码分析1—上层samples分析
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • 远离DoS攻击 Windows Server 2016发布DNS政策
  • 运行时添加log4j2的appender
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • 专访Pony.ai 楼天城:自动驾驶已经走过了“从0到1”,“规模”是行业的分水岭| 自动驾驶这十年 ...
  • 资深实践篇 | 基于Kubernetes 1.61的Kubernetes Scheduler 调度详解 ...
  • ​Spring Boot 分片上传文件
  • ​VRRP 虚拟路由冗余协议(华为)
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • #laravel 通过手动安装依赖PHPExcel#
  • #laravel部署安装报错loadFactoriesFrom是undefined method #
  • #我与Java虚拟机的故事#连载10: 如何在阿里、腾讯、百度、及字节跳动等公司面试中脱颖而出...
  • #周末课堂# 【Linux + JVM + Mysql高级性能优化班】(火热报名中~~~)
  • $var=htmlencode(“‘);alert(‘2“); 的个人理解
  • (51单片机)第五章-A/D和D/A工作原理-A/D
  • (博弈 sg入门)kiki's game -- hdu -- 2147
  • (七)glDrawArry绘制
  • (四)JPA - JQPL 实现增删改查
  • (算法)求1到1亿间的质数或素数
  • (一)Mocha源码阅读: 项目结构及命令行启动
  • (原創) 人會胖會瘦,都是自我要求的結果 (日記)
  • (转)visual stdio 书签功能介绍
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • ***检测工具之RKHunter AIDE
  • .mysql secret在哪_MySQL如何使用索引
  • .NET Core 发展历程和版本迭代
  • .NET/C# 使用反射注册事件
  • .NET开源纪元:穿越封闭的迷雾,拥抱开放的星辰