SpringBoot是如何实现自动装配的

628 阅读6分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战

一:简述

SpringBoot作为当前最火的java开发框架,它的自动装配帮助我们省略了许多繁琐配置,能够帮助我们快速构建一个项目,那么今天我们就一起分析下SpringBoot实现自动装配的原理。

二:准备工作

Spring的自动装配是基于Spring的SPI机制和@Import注解来实现的。所以我们先简单了解下Spring的SPI机制以及@Import注解的作用。

1:SPI机制

a.什么是SPI

SPI:全称 Service Provider Interface,是一种服务发现机制,它是一种约定大于配置的思想,约定好配置文件路径,配置文件的名称,配置的定义方式等,然后可以通过配置文件的方式来加载需要的类。

注:在jdk,dubbo中也都有定义自己的SPI实现,后续的文章会讲到,感兴趣的同学也可以自己去研究下。

b. Spring中的SPI

spi是一种约定大于配置的思想,所以在使用Spring的SPI时我们需要遵守它的约定。Spring的SPI约定我们要想通过SPI来加载类,需要在ClassPath路径下的META-INF文件夹下定义一个名称为spring.factories的文件,并且它的配置文件的配置方式必须是key-value的形式进行配置。

c. Spring的SPI的使用

1.首先创建一个META-INF文件夹,并且在META-INF下创建一个spring.factories的文件。

2.在配置文件中以key—value的形式书写我们的配置(value如果多个,那么以逗号分割)。

例如:

com.example.Log=com.example.Log4J,com.example.LogBack

3.Spring已经将读取配置文件,解析配置文件的逻辑封装好了,我们只需要利用SpringFactoriesLoader工具类的api就可以获取到加载的信息。

SpringFactoriesLoader提供了两个方法获取spi的配置.

1.loadFactories()

loadFactories()方法要求读取的配置key是一个父类(或者接口)的全类名,value是这个key的子类的全类名。它可以用来获取配置value的实例。

例如:

spring.factories 配置文件:

com.example.Log=com.example.Log4J,com.example.LogBack

代码:

//Log4J,LogBack必须是Log的子类
List<Log> logs = SpringFactoriesLoader.loadFactories(Log.class, this.getClass().getClassLoader());
        //获取到配置文件value的实例
        for (Log log : logs){
            System.out.println(log);
        }

2.loadFactoryNames()

loadFactoryNames()要求key是一个全类名,而value没有限制。loadFactoryNames()可以用来获取配置value的值。

例如:

spring.factories 配置:

com.example.Log=com.example.Log4J,com.example.LogBack

代码:

List<String> classNames = SpringFactoriesLoader.loadFactoryNames(Log.class, this.getClass().getClassLoader());
        //获取到value的值 以字符串集合的方式返回
        System.out.println(classNames);

2:@Import注解的作用

分为以下几种情况:

a. @Import的value属性为@Configuration的配置类或普通类

作用:将类的实例加入到Spring IoC容器中

b. @Import的value属性是 ImportSelector接口的实现类

作用:将selectImports()接口返回的类的实例加入到Spring IoC容器中

c. @Import的value属性为DeferredImportSelector接口的实现类

作用:首先会通过DeferredImportSelector的getImportGroup()方法获取的Group,然后调用Group的process()和selectImports(),将selectImports()返回的类的实例加入到Spring IoC容器中。

d. @Import的value属性是ImportBeanDefinitionRegistrar接口的实现类

作用:可以通过实现ImportBeanDefinitionRegistrar的registerBeanDefinitions()方法将需要注入的类的实例加入到Spring IoC容器中。

注:在我的另外一篇文章中有对@Import注解的作用和原理的详细说明 原文地址:@Import注解的使用和原理

三:自动装配原理分析

通过第二节的铺垫,相信大家已经对Spring的SPI和@Import注解都有了一定的了解,那么我们现在对SpringBoot的自动装配原理进行分析。

我们首先看@SpringBootApplication注解,它是一个复合注解,由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan等注解复合而成,而自动装配是基于@EnableAutoConfiguration注解来实现的,所以我们重点分析@EnableAutoConfiguration注解。

1643340547(1).jpg

@EnableAutoConfiguration

@EnableAutoConfiguration主要是通过@Import导入AutoConfigurationImportSelector注解实现的自动装配,而AutoConfigurationImportSelector是一个实现了DeferredImportSelector接口的类。

1643340635(1).png

1643340766(1).png

如果@Import注解导入的类实现了DeferredImportSelector接口,那么首先会执行它的getImportGroup()。然后会根据getImportGroup()返回的Group,执行对应Group的process()方法和selectImports()方法(如果getImportGroup()返回null,那么会使用默认的Group:DefaultDeferredImportSelectorGroup),然后将selectImports()方法返回的类的实例加入到Spring容器中。

	public Class<? extends Group> getImportGroup() {
		return AutoConfigurationGroup.class;
	}

getImportGroup()方法返回AutoConfigurationGroup,所以接下来会执行AutoConfigurationGroup的process()方法和selectImports()收集需要导入的类,并且将selectImports()返回的类加入到Spring容器中。所以我们主要分析AutoConfigurationGroup的process()方法和selectImports()方法。

首先我们看process()方法做了什么

process()

process()调用getAutoConfigurationEntry方法获取到需要自动装配的类,并将它们保存在 autoConfigurationEntries中。

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()));
                        //通过getAutoConfigurationEntry()方法获取需要自动转配的类
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

getAutoConfigurationEntry()

getAutoConfigurationEntry()通过getCandidateConfigurations()方法获取自动装配的类,然后会进行去重,过滤不需要导入的类(包括application.properties文件中的spring.autoconfigure.exclude配置的类,@SpringBootApplication注解配置的exclude和excludeName的类,AutoConfigurationImportFilter的match()方法返回true的类),然后将处理完的类封装成AutoConfigurationEntry。

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);
                //获取spring.factories中AutoConfigurationImportFilter的实现类并且循环调用它们的match()方法
		configurations = getConfigurationClassFilter().filter(configurations);
                //发布自动装配的事件
		fireAutoConfigurationImportEvents(configurations, exclusions);
                //封装成AutoConfigurationEntry返回
		return new AutoConfigurationEntry(configurations, exclusions);
	}

getCandidateConfigurations()

getCandidateConfigurations()方法通过SPI的方式获取key为EnableAutoConfiguration全类名的value。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
                //通过spi机制获取需要自动装配的类
		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;
	}

selectImports()

public Iterable<Entry> selectImports() {
			if (this.autoConfigurationEntries.isEmpty()) {
				return Collections.emptyList();
			}
                        //获取所有的需要排除的类
			Set<String> allExclusions = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
                        //获取需要导入的类
			Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
					.collect(Collectors.toCollection(LinkedHashSet::new));
			processedConfigurations.removeAll(allExclusions);

                        //根据AutoConfigureOrder进行排序 然后将导入的类封装成Entry返回。
			return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
					.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
					.collect(Collectors.toList());
		}

Spring在处理@Import注解的时候会将selectImports()方法返回的类的实例加入到Spring容器中,也就完成了自动装配。

四:总结

自动装配主要是根据@Import注解和SPI机制来完成的,所以要理解自动装配首先需要了解@Import注解和SPI机制。大致的流程就是通过SPI机制获取到spring.factories中key为EnableAutoConfiguration的value(需要自动装配的全类名),然后将获取到的类名通过@Import注解将对应的实例加入到Spring容器中。

如果文章对你有帮助,那么点个赞再走吧.