【Spring】深究SpringBoot自动装配原理

news2024/12/23 20:24:47

文章目录

    • 前言
    • 1、main入口
    • 2、@SpringBootApplication
    • 3、@EnableAutoConfiguration
    • 4、AutoConfigurationImportSelector
      • 4.1、selectImports()
      • 4.2、getAutoConfigurationEntry()
      • 4.3、getCandidateConfigurations()
      • 4.4、loadFactoryNames()
    • 5、META-INF/spring.factories
    • 6、总结

前言

早期的Spring项目需要添加需要配置繁琐的xml,比如MVC、事务、数据库连接等繁琐的配置。Spring Boot的出现就无需这些繁琐的配置,因为Spring Boot基于约定大于配置的理念,在项目启动时候,将约定的配置类自动装配到IOC容器里。

这些都因为Spring Boot有自动装配的特性。

接下来将会逐步从源码跟踪进去,一步步掀开自动装配的面纱。

1、main入口

在SpringBoot项目的启动类中,注解SpringBootApplication是必须需要添加的,既然是从这里启动的,那么自动装配的操作应该也是在启动的时候去执行的吧,我们一步步挖进去一探究竟。

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

2、@SpringBootApplication

点进来@SpringBootApplication注解之后会发现,这玩意头顶怎么挂着这么多注解的。不要着急,与Bean注入也只有三个相关,而自动装配的核心注解也是只有一个:

  • @SpringBootConfiguration:继承自Spring的 @Configuration 注解,作用也大致相同,支持在入口处通过@Bean等注解手动配置一下 Bean 加入到容器中;
  • @EnableAutoConfiguration:顾名思义,这玩意就是用来自动装配的,都写在人家脸上了,接下来主要的介绍核心也是该注解;
  • @ComponentScan:告诉Spring需要扫描哪些包或类,如果不设值的话默认扫描@ComponentScan注解所在类的同级类和同级目录下的所有类,这也是为什么放置启动类位置有要求的原因。
@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 {
    // 省略该注解内属性和方法
    // ......
}

3、@EnableAutoConfiguration

点进来@EnableAutoConfiguration注解,去掉那些有的没的注解,可以初步发现与自动装配有关的应该是有两个,分别是注解@AutoConfigurationPackage和导入的类AutoConfigurationImportSelector

点进去注解@AutoConfigurationPackage发现里面没有什么有用的信息,其作用是将添加该注解的类所在的package 作为自动装配 package 进行管理,感兴趣的小伙伴可以自行点进去查看。

AutoConfigurationImportSelector作为被导入的类,也是实现自动装配的核心类之一,接下来将会点进去查看其细节。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    // 省略该注解内属性和方法
    // ......
}

4、AutoConfigurationImportSelector

4.1、selectImports()

AutoConfigurationImportSelector类中,自动装配核心方法为selectImports(),在其文档中是这么介绍该方法的:

Select and return the names of which class(es) should be imported based on the AnnotationMetadata of the importing @Configuration class.

Returns: the class names, or an empty array if none

根据@Configuration中的AnnotationMetadata,选择并返回应该被导入的类的名称。

返回:类名,如果不存在则返回空数组

可以看到从这里便已经获取被设置需要自动装配的类的信息了,从源码中可以看到在selectImports()中的核心方法应该是getAutoConfigurationEntry()

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    // 省略类中成员属性...
            
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
    
    // 省略类中其余方法...
}

4.2、getAutoConfigurationEntry()

getAutoConfigurationEntry()方法同样处于AutoConfigurationImportSelector类中,在刚方法中主要返回的是已经配置好的配置数组,该配置数组被包装在类中,其中AutoConfigurationImportSelector.AutoConfigurationEntry的介绍如下:

Create an entry with the configurations that were contributed and their exclusions.

Params: configurations – the configurations that should be imported exclusions – the exclusions that were applied to the original list

使用所提供的配置及其排除项创建一个条目。

参数:configurations ——应该导入的配置 exclusions – 应用于原始列表的排除项

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 获取应考虑的自动配置类名称
	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	configurations = removeDuplicates(configurations);
    // 获取被排除的数据
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	checkExcludedClasses(configurations, exclusions);
	configurations.removeAll(exclusions);
	configurations = getConfigurationClassFilter().filter(configurations);
	fireAutoConfigurationImportEvents(configurations, exclusions);
    // 封装应考虑的自动配置类名称和被排除的数据
	return new AutoConfigurationEntry(configurations, exclusions);
}

4.3、getCandidateConfigurations()

方法getCandidateConfigurations()同样处于AutoConfigurationImportSelector类中,该方法主要用于获取应考虑的自动装配类名称,而获取候选项的方法为其中的loadFactoryNames()方法。

Return the auto-configuration class names that should be considered. By default this method will load candidates using ImportCandidates with getSpringFactoriesLoaderFactoryClass().

返回应考虑的自动配置类名称。默认情况下,此方法将使用ImportCandidates和getSpringFactoriesLoaderFactoryClass()加载候选项。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	List<String> configurations = new ArrayList<>(
        	// 获取候选项
			SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
	ImportCandidates.load(AutoConfiguration.class, 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;
}

4.4、loadFactoryNames()

挖到这里其实就差不多到头了,再往下就不太礼貌了。

在这里干的活在官方给出的文档中都说的很简单明了了:

Load the fully qualified class names of factory implementations of the given type from “META-INF/spring.factories”, using the given class loader.

As of Spring Framework 5.3, if a particular implementation class name is discovered more than once for the given factory type, duplicates will be ignored.

使用给定的类加载器,从"META-INF/spring.factories"加载给定类型的工厂实现的完全限定类名。

从 Spring Framework 5.3 开始,如果针对给定工厂类型多次发现特定实现类名称,则将忽略重复项。

从介绍中就可以看到所谓的自动装配,就是将META-INF/spring.factories中写明白的内容给加载出来,同时连上面提及到的排除项都写明白在这里了,而源码也是对官方文档进行直接翻译了,猜测是出于封装性和代码简洁度考虑,开发人员将核心逻辑封装成了私有方法loadSpringFactories()

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

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 {
        // 获取资源,常量FACTORIES_RESOURCE_LOCATION的值为META-INF/spring.factories
		Enumeration<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.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;
}

5、META-INF/spring.factories

spring-boot-autoconfigure.jar 包中的 META-INF/spring.factories 里面默认配置了很多aoto-configuration,如下:

image-20230802165739779

WebMvcAutoConfiguration为例:

package org.springframework.boot.autoconfigure.web.servlet;

@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }

    @Bean
    @ConditionalOnMissingBean(HttpPutFormContentFilter.class)
    @ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true)
    public OrderedHttpPutFormContentFilter httpPutFormContentFilter() {
        return new OrderedHttpPutFormContentFilter();
    }
    
    ......etc
}

这里有一个地方不知道大家有没有注意到,有大部分的注解前面都有Conditional字样的,这其实也是SpringBoot的强大之处之一,其使用了 Spring 4 框架的新特性:@Conditional注释,此注解使得只有在特定条件满足时才启用一些配置。这也 Spring Boot “智能” 的关键注解。Conditional大家族如下:

注解描述
@ConditionalOnBean当容器中存在指定的Bean时生效
@ConditionalOnClass当类路径中存在指定的类时生效
@ConditionalOnExpression通过SpEL表达式指定条件,满足条件时生效
@ConditionalOnMissingBean当容器中不存在指定的Bean时生效
@ConditionalOnMissingClass当类路径中不存在指定的类时生效
@ConditionalOnNotWebApplication当应用程序不是Web应用时生效
@ConditionalOnResource当指定的资源存在于类路径中时生效
@ConditionalOnWebApplication当应用程序是Web应用时生效

6、总结

SpringBoot自动配置原理如下:

  1. @EnableAutoConfiguration 注解导入 AutoConfigurationImportSelector 类;
  2. 执行 selectImports() 方法调用 SpringFactoriesLoader.loadFactoryNames() 扫描所有 jar 下面的对应的 META-INF/spring.factories 文件;
  3. 限定为 @EnableAutoConfiguration 对应的 value,将这些装配条件的装配到 IOC 容器中。

自动装配简单来说就是自动将第三方的组件的 bean 装载到 IOC 容器内,不需要再去写 bean 相关的配置,符合约定大于配置理念。SpringBoot 基于约定大于配置的理念,配置如果没有额外的配置的话,就给按照默认的配置使用约定的默认值,按照约定配置到 IOC 容器中,无需开发人员手动添加配置,加快开发效率。

同时,对于自己开发SDK时,也可利用 SpringBoot 自动装配原理,编写自己的 META-INF/spring.factories 文件,从而将某些特定需求的类生成 Bean 放入容器中。

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

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

相关文章

以beam search为例,详解transformers中generate方法(下)

以beam search为例&#xff0c;详解transformers中generate方法&#xff08;下&#xff09; 1. beam search原理回顾2. 代码流程概览3. BeamSearchScorer4. BeamHypotheses5. beam_search过程5.1 beam score初始化5.2 准备输入5.3 前向forward5.4 计算下一个step每个token的得分…

网络安全知识点整理(作业2)

目录 一、js函数声明->function 第一种 第二种 第三种 二、this关键字 this使用场合 1.全局环境 2.构造函数 3.对象的方法 避免多层this 三、js的同步与异步 定时器 setTimeout和setInterval 同步与异步的例子 四、宏任务与微任务 分辨宏任务与微任务 一、js…

深度学习——划分自定义数据集

深度学习——划分自定义数据集 以人脸表情数据集raf_db为例&#xff0c;初始目录如下&#xff1a; 需要经过处理后返回 train_images, train_label, val_images, val_label 定义 read_split_data(root: str, val_rate: float 0.2) 方法来解决&#xff0c;代码如下&#xff1a…

【C++】开源:matplotlib-cpp静态图表库配置与使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍matplotlib-cpp图表库配置与使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&…

RK3588开发板 (armsom-w3) 之 USB摄像头图像预览

硬件准备 RK3588开发板&#xff08;armsom-w3&#xff09;、USB摄像头&#xff08;罗技高清网络摄像机 C93&#xff09;、1000M光纤 、 串口调试工具 v4l2采集画面 v4l2-ctl是一个用于Linux系统的命令行实用程序&#xff0c;用于控制视频4 Linux 2&#xff08;V4L2&#xff0…

晚读“散文”一篇之随感

近来天气太热&#xff0c;上网写作的激情锐减&#xff0c;午后“昏睡百年”至近5点半才睡眼惺忪地起床。因深陷上网日日写作长达14年之久&#xff0c;也便如同“吸粉成瘾”的“瘾君子”戒不了毒瘾一样管束不了自己的“鼠标手”&#xff0c;就打开了电脑。 恍惚间步入了网络上的…

Dockerfile构建apache镜像(源码)

Dockerfile构建apache镜像&#xff08;源码&#xff09; 1、建立工作目录 [rootdocker ~]# mkdir apache [rootdocker ~]# cd apache/ 2、编写Dockerfile文件 [rootdocker apache]# vim Dockerfile #基于的基础镜像 FROM centos:7#镜像作者信息 MAINTAINER Huyang <133…

Java通过freemark创建word文档

创建freemarker模板 创建Freemarker模板&#xff1a;在您的Java项目中&#xff0c;创建一个Freemarker模板文件&#xff08;例如template.ftl&#xff09;&#xff0c;其中包含您想要生成的Word文档的内容。您可以在模板中使用Freemarker的标记来插入动态内容。 <!DOCTYPE…

Spring如何通过三级缓存解决循环依赖问题?

目录 一、什么是Spring 二、循环依赖问题 三、三级缓存机制 四、如何通过三级缓存解决循环依赖问题 一、什么是Spring Spring框架是一个开源的Java应用程序开发框架&#xff0c;提供了一种全面的、一致的编程模型&#xff0c;用于构建企业级应用程序和服务。它由Rod Johnso…

如何压缩高清PDF文件大小?将PDF文件压缩到最小的三个方法

PDF格式是一种非常常用的文档格式&#xff0c;但是有时候我们需要将PDF文件压缩为更小的大小以便于传输和存储。在本文中&#xff0c;我们将介绍三种PDF压缩的方法&#xff0c;包括在线PDF压缩、利用软件PDF压缩以及使用WPS缩小pdf。 首先&#xff0c;在线PDF压缩是最常用的方…

人体大脑神经元运行模拟器!让你直观体验大脑的运行方式

首先&#xff0c;宣布沾花把玖正式回归&#xff01;&#xff01;&#xff01; 最近沾花在网上看到一个神奇的网站&#xff1a;A Neural Network Playground 经过沾花的亲手测试&#xff0c;发现这玩意儿能模拟人体大脑神经元的运行&#xff01; 下面是网址&#xff1a; A N…

干货!机器视觉基础知识汇总

现如今,中国已经成为世界机器视觉发展最为活跃地区,应用范围涵盖了工业、农业、医药、军事、航天、气象等国民经济各个行业。虽然机器视觉的成长速度非常快,但是还是有很多人对机器视觉并不了解,今天我们来了解下机器视觉。 机器视觉就是用机器代替人眼来做测量和判断。机器…

一条自由游动的鲸鱼

先看效果&#xff1a; 再看代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>鲸鱼</title><style>#canvas-container {width: 100%;height: 100vh;overflow: hidden;}&l…

Linux(二)---------网络命令学习(ifconfig命令)

1.ifconfig命令 用于配置网卡ip地址信息&#xff0c;等网络参数信息&#xff0c;或者查看显示网络接口信息&#xff0c;类似于windows的ipconfig命令&#xff0c;还能够临时性的配置ip地址&#xff0c;子网掩码&#xff0c;广播地址&#xff0c;网关信息等。 注意ifconfig命令…

配置GIt账号、配置公钥

1.设置账号和邮箱 打开终端输入以下命令&#xff1a; git config --global --unset-all user.name git config --global --unset-all user.email然后输入以下命令来设置新的账号和邮箱&#xff1a; git config --global user.name "your_username" git config --glo…

整理了250个shell脚本,拿来即用!

无论是系统运维&#xff0c;还是应用运维&#xff0c;均可分为“纯手工”→ “脚本化”→ “自动化”→“智能化”几个阶段&#xff0c;其中自动化阶段&#xff0c;主要是将一些重复性人工操作和运维经验封装为程序或脚本&#xff0c;一方面避免重复性操作及风险&#xff0c;另…

【音视频SDK测评】线上K歌软件开发技术选型

摘要 在线K歌软件的开发有许多技术难点&#xff0c;需考虑到音频录制和处理、实时音频传输和同步、音频压缩和解压缩、设备兼容性问题等技术难点外&#xff0c;此外&#xff0c;开发者还应关注音乐版权问题&#xff0c;确保开发的应用合规合法。 前言 前面写了几期关于直播 …

qt系列-qt6在线安装慢的问题

.\qt-unified-windows-x64-online.exe --mirror https://mirrors.aliyun.com/qt/下载速度飞快

剑指offer19.正则表达式

这道题我一看就有印象&#xff0c;我室友算法课设抽到这题&#xff0c;他当时有个bug让我帮他看一下&#xff0c;然后我就大概看了一下他的算法&#xff0c;他是用动态规划写的&#xff0c;用了一个二维数组&#xff0c;然后我就试着按照这个思路去写&#xff0c;想了一会还是没…

输入筛选框搜索

文章目录 输入筛选框实现效果图需求前端工具版本添加依赖main.js导入依赖 代码 后端代码对应 sql对应 mapper.xml 文件的动态 sql 输入筛选框实现 效果图 需求 通过筛选框&#xff0c;选择公司&#xff0c;传入后端&#xff0c;后端根据公司名称去文章的内容中进行模糊查询 …