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

SpringBoot的自动装配

一. 什么是SpringBoot自动装配

SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器,并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。

通俗来说,在我们没有使用SpringBoot之前,如果我们需要引入一个三方依赖或者组件,需要进行很多的配置才能实现,但是在使用了SpringBoot之后,我们只需要引入一个maven依赖即可完成,比如引入Redis到我们的项目中,就只需要加入依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

然后进行一下Redis相关的地址配置即可,这种就是SpringBoot的自动装配机制

我们本篇分析的是自动装配的过程,所以重点只看自动装配相关的代码,关于过程中其他的SpringBoot启动的相关流程,后续会有文章进行分析。

二. 自动装配如何实现的

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Test1Application {

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

}

一个普通的SpringBoot启动类,我们来看他的注解@SpringBootApplication
在这里插入图片描述

可以看到@SpringBootApplication实际上是一个组合注解,去除掉前四个元注解,剩下的

  • @SpringBootConfiguration 其实就是一个@Configuration注解
  • @EnableAutoConfiguration 这个注解是自动装配的关键,其中有两个@Import注解引入了自动装配需要的类:@Import(AutoConfigurationImportSelector.class)@Import(AutoConfigurationPackages.Registrar.class)
  • @ComponentScan 定义了包扫描的信息

这三个注解中,@EnableAutoConfiguration 表示开启自动装配,并引入了AutoConfigurationImportSelector这个自动装配的关键类。接下来我们从SpringApplication.run开始顺序分析自动装配的过程

三. 自动装配过程解析

从启动类的SpringApplication.run进入开始分析,回来到这一步:

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}

这个方法分两步,一个是new SpringApplication一个是调用new出来对象实例的run方法,我们先看new SpringApplication

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}

这段代码是创建SpringApplication的构造方法,我们重点了来看其中的getSpringFactoriesInstances(ApplicationContextInitializer.class)这一步。至于后面的getSpringFactoriesInstances(ApplicationListener.class)和前面的一样流程,只不过是从缓存中取筛选数据进行实例化,所以就不再进行第二次分析了。
代码跟进会来到:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = getClassLoader();
	// Use names and ensure unique to protect against duplicates
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

在上面这个方法中,我们首先要清楚的是入参typeApplicationContextInitializer.class,然后重点来看SpringFactoriesLoader.loadFactoryNames(type, classLoader)

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
	String factoryTypeName = factoryType.getName();
	return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

这个方法也是分两个步骤,一个是loadSpringFactories(classLoader)一个是根据入参的类型名称从中获取需要的内容,其实就是map.getOrDefault方法。我们重点跟进loadSpringFactories(classLoader)

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
	// 当SpringBoot启动并第一次调用该方法时,这个cache肯定是空的,所以获取不到内容
	MultiValueMap<String, String> result = cache.get(classLoader);
	if (result != null) {
		return result;
	}

	try {
		// 通过classLoader加载META-INF/spring.factories资源  
		// 其实就是SpringBoot项目下的所有META-INF/spring.factories文件内容
		Enumeration<URL> urls = (classLoader != null ?
				classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
		// 创建一个多value的map 
		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 factoryTypeName = ((String) entry.getKey()).trim();
				for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					result.add(factoryTypeName, factoryImplementationName.trim());
				}
			}
		}
		cache.put(classLoader, result);
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load factories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}

上面这个方法其实算是自动装配中的第一个比较重要的步骤,加载所有的META-INF/spring.factories内容,这里贴出来一个

相关文章:

  • 随州小程序开发,免费试用60天
  • Java 安全框架Apache Shiro和Spring Security比较
  • 解决本地项目连接虚拟机redis进程失败【Failed connecting to host 6379】
  • 跨平台应用开发进阶(三十四) :uni-app 实现微信分享
  • Resnet残差网络|卷积神经网络|原理|新人总结
  • QCC51XX---TwsTopology_Init(goals分析)
  • impala添加kerberos认证
  • 关于在vue中使用eventBus接收监听被多次触发的解决办法
  • 【跨境电商】用WhatsApp群发拯救购物车弃购问题
  • Python数据分析之Pandas时间序列详解2
  • Go/Golang语言学习实践[回顾]教程08--通过时间判断时辰的示例【下】
  • 考生注意|2022年度北京注册安全工程师考试时间延迟
  • C++基础 类和对象
  • Redis中:Llist、Set、Hash、Zset的常用指令
  • 使用数据可观测性减少Confluent Cloud Kafka 运营成本的五种方式
  • java-php-python-ssm爱音乐网站计算机毕业设计
  • Eclipse Theia技术揭秘——构建桌面IDE
  • Kafka 安装、使用
  • Acrylamide-PEG-acid,ACA-PEG-COOH,丙稀酰胺-聚乙二醇-羧基可用于修饰多肽
  • 计算机毕业设计 SSM中医商城管理系统(源码+论文)
  • 2022全国车辆工程专业大学排名一览表
  • 2022周口职业技术学院单招学费多少钱一年-各专业收费标准
  • 2022年中原工学院艺术类招生简章
  • 2022浙江经贸职业技术学院学费多少钱一年-各专业收费标准
  • 2022年湖南大学强基计划报名条件-报名时间-报名入口
  • 2020河北工程大学运动训练专业招生简章
  • 2022湖州有哪些民办大学?湖州所有民办大学名单一览表(1所)
  • 2022天津城市建设管理职业技术学院学费多少钱一年-各专业收费标准
  • 2022滁州学院艺术类学费多少钱一年-各专业收费标准
  • 2022云南警官学院学费多少钱一年-各专业收费标准