IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 面试被问Springboot自动装配原理,不好意思,拿捏了!!!收藏不亏 -> 正文阅读

[Java知识库]面试被问Springboot自动装配原理,不好意思,拿捏了!!!收藏不亏

springboot 版本 2.5.5,blog内容都是个人理解。欢迎留言讨论~

Debug路线图

说多都是泪,大家看图。
在这里插入图片描述

让我们从run说起

用了这么多年的的Springboot,这个 run() 方法到底做了些什么事呢?

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

归属

run() 方法归属于 SpringApplication.class 对象,所以在调用run() 方法前,需要先实例化 SpringApplication.class 对象:

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

SpringApplication.class 对象实例化时,都做了些什么事呢?
这里主要看需要注意两个方法:①getSpringFactoriesInstances() 和 ②deduceMainApplicationClass()

/**
 * 实例化时,实际调用的构造方法
 */
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		// 这里将spring.
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
		// 设置初始化器
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		// 设置监听器
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

getSpringFactoriesInstances()方法主要加载整个应用程序中的 spring.factories 文件,将文件的内容放到缓存对象中,方便后续获取使用。

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		// 初次加载,cache中获取不到数据,所以为null
		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中
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

deduceMainApplicationClass() 方法返回了启动类的类信息:
在这里插入图片描述

总结

实例化SpringApplication.class 对象完成了两件事:

1.加载整个应用程序中的 spring.factories 文件,将文件的内容放到缓存对象中,方便后续获取使用。
2.返回了启动类的类信息。

run

有了SpringApplication.class 对象实例对象,接下来就可以调用run() 方法。

run() 方法中,我们主要关注两个方法prepareContext()refreshContext()

public ConfigurableApplicationContext run(String... args) {
// 省略部分代码
	try {
		prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
		refreshContext(context);
	}
// 省略部分代码

prepareContext()方法准备上下文环境,通过调用load()方法加载启动类,为获取启动类上的注解做准备;

private void load(Class<?> source) {
		if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
			// Any GroovyLoaders added in beans{} DSL can contribute beans here
			GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
			((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
		}
		// 这里会判断启动类不是一个groovy闭包也不是一个匿名类
		if (isEligible(source)) {
			// 注册读取启动类的注解信息
			// 注意,这里将启动类型注册为AnnotatedBeanDefinition类型,后面parse()解析时会用到。
			this.annotatedReader.register(source);
		}
	}

refreshContext() 方法最终调用了AbstractApplicationContext.class 类的 refresh(),这里相信看过spring源码的小伙伴都很熟悉 refresh() 这个方法。

自动装配操作的主战场主要是在 ①invokeBeanFactoryPostProcessors() 方法,①调用了②invokeBeanDefinitionRegistryPostProcessors() 方法,②调用了ConfigurationClassPostProcessor.class 的③postProcessBeanDefinitionRegistry()方法,③调用了 ④processConfigBeanDefinitions() 方法;

processConfigBeanDefinitions() 方法中:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();
		// 这里会循环匹配到启动类,并且添加到上面的configCandidates集合中。
		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}
		
		// ...
		
		// 解析每一个标注了@Configuration注解的类,启动类上的@SpringBootApplication就包含了@Configuration注解
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);
				Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
			// 开始解析
			parser.parse(candidates);
			parser.validate();
	}
		// ============ 分割线 =================
/**
 * 为了方便阅读,这里将parse()方法接入
 */
public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				// 在前面prepareContext()方法中的load()方法中已经说过,所以这会进入这个判断解析
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}	
		// ...
		}
		// 注意!!这里,parse()解析完成后,会回到这里执行process()方法;
		this.deferredImportSelectorHandler.process();
	}

进入判断后parse()方法会接着调用 ①processConfigurationClass()方法,①调用②doProcessConfigurationClass()方法;

doProcessConfigurationClass()中又开始对注解进行进一步的解析,包括@PropertySource、@ComponentScan、@Import(咱们看这个)、@ImportResource、@Bean,解析之前,会通过getImports()方法调用collectImports()方法,统计出被@Import标注的类型信息;

	protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {
		// ...
		// Process any @Import annotations
		processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
		// ...
// ============ 分割线 =================
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
		Set<SourceClass> imports = new LinkedHashSet<>();
		Set<SourceClass> visited = new LinkedHashSet<>();
		collectImports(sourceClass, imports, visited);
		return imports;
	}
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
			throws IOException {

		if (visited.add(sourceClass)) {
			for (SourceClass annotation : sourceClass.getAnnotations()) {
				String annName = annotation.getMetadata().getClassName();
				if (!annName.equals(Import.class.getName())) {
					// 注意看这里,自调用递归查询
					collectImports(annotation, imports, visited);
				}
			}
			imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
		}
	}
}
  • getImports() 方法查询结果展示:
    在这里插入图片描述

parse()方法解析完成 @Import 注解后(这里忘记的小伙伴,可看看上面的parse()方法,我有代码注释),接着开始调用①process()方法,①中调用②processGroupImports()方法,②中接着调用 ③grouping.getImports()方法,③调用DeferredImportSelector.Group 接口的 ④process()方法,这里我们看它的实现类 AutoConfigurationImportSelector.class 实现的 ④process()方法(这里需要留意一下,一会还会回来用到),④调用了 ⑤getAutoConfigurationEntry(),⑤中调用了⑥getCandidateConfigurations() 方法;

重点来了:经过上面的一系列方法调用,终于来到这个方法,相信大家在许多博客里都又看到过 ⑥getCandidateConfigurations() 这个方法,但又没有说清楚具体是怎么调用到这个方法的,只是说了这个类会得到待配置的class的类名集合等等;

在⑥这个方法中,显示通过 ⑦getSpringFactoriesLoaderFactoryClass() 这个方法返回了一个EnableAutoConfiguration.class 注解对象,然后又通过调用 ⑧loadFactoryNames(),⑧又调用了 ⑨loadSpringFactories()

⑧⑨方法看着是不是比较眼熟? 是的,我们在初始化SpringApplication对象时,曾调用过这两个方法,在调用⑨时,将 spring.factories 文件的内容放到cache缓存对象中。

@Override
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		// getSpringFactoriesLoaderFactoryClass()这个方法返回了一个EnableAutoConfiguration.class注解对象
		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;
}
// ===========================分割线===========================
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
	// ...

此时的cache对象中存在EnableAutoConfiguration对象,size=131个:
在这里插入图片描述
这131个就是 spring.factories 文件中的自动装配配置项:
在这里插入图片描述
当然,这里面有许多我们没有用到类信息也被装配了进来,这里不要着急接着往下看,装配完成后回到了⑤getAutoConfigurationEntry()方法中,且返回了一个List< String>的一个配置类信息集合,接着又做了些什么事?
在这里插入图片描述
从上图可以看出,131个配置信息,经过过滤移除后,最终变成13个需要使用的,拿到最终配置信息,(愣着干嘛,赶紧撒花呀!),到这里自动装配过程基本上就结束了。

这里的结束是指自动装配过程结束,也就是我们 refresh()中的invokeBeanFactoryPostProcessors() 方法执行结束,当然这个方法还做很多别的事,但是本文只关注自动装配相关,完成此方法后并不表示类就已经实例化完成,这里只是将类信息装配到了spring容器中,后续会有别的方法完成类的实例化。(实例化看它:finishBeanFactoryInitialization())
在这里插入图片描述

再说说注解

@SpringBootApplication 是的没错,这个注解大家都熟悉,springboot 项目启动类上都有:
在这里插入图片描述
@SpringBootApplication 中包含了两个注解:

  • @EnableAutoConfiguration(重点):启用 SpringBoot 的自动配置机制;
  • @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类;
  • @SpringBootConfiguration:允许在上下文中注册额外的 bean 或导入其他配置类;

三个注解中,自动装配的核心 @EnableAutoConfiguration 就是这个注解:
在这里插入图片描述
@EnableAutoConfiguration 注解通过 Spring 提供的 @Import 注解导入了 AutoConfigurationImportSelector.class类(@Import 注解可以导入配置类或者 Bean 到当前类中),这个类的作用在上也说过(获取spring.factories文件中待配置的class的类名集合)


感谢各位大佬的:点赞、收藏和评论!

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-10-12 23:18:12  更:2021-10-12 23:18:27 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 21:40:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码