1 前言

之前也写过一篇相似的文章,可是当时了解的并不是很深入,所以一直想重新写,可是一直没有时间,就拖到了现在。这篇文章可能会很长,因为在解说主动装备的进程中还会衍生出其他一些重要的知识点,我也会进行介绍。

2 热身事例

要谈 SpringBoot 的主动安装,肯定离不开下面这段代码。

SpringBoot自动配置原理详解

其实我学习 SpringBoot 时,根本没有思考过这段代码,只知道这是 SpringBoot 程序的发动类,要保证他能够掩盖一切要扫描的包,然后就去写代码了。

咱们先来看看这段代码:

ConfigurableApplicationContext run = SpringApplication.run(AppRunApplication.class, args);

这段代码的效果是发动 SpringBoot 运用程序,并回来一个 ConfigurableApplicationContext 目标的实例。

SpringApplication.run() 办法回来的是 ConfigurableApplicationContext 类型的目标,表明 Spring 运用程序的上下文。这个上下文保存了Spring 容器中一切 bean 的引用,关于运用程序中的其他组件来说,这个上下文便是一个大局同享的容器。

SpringApplication.run(AppRunApplication.class, args) 句子的含义是依据 AppRunApplication 这个类发动 SpringBoot 运用程序,并将命令行参数传递给运用程序。运转这个句子后,Spring会主动进行运用程序的初始化工作,包含创立ApplicationContext、注册一切的bean界说、发动嵌入式Web容器、加载运用程序的装备文件等。

此外,经过这个办法还能够获取到 ConfigurableApplicationContext 的实例,运用这个目标能够进一步操控运用程序的运转,如手动关闭运用程序、获取运用程序的环境变量、添加自界说的bean等。

只看文字实在时太单调了,咱们仍是看看代码吧。咱们在发动类,创立了一个 Test 类,然后生命了一个 Bean 办法,它的效果便是回来一个 Test 类,然后咱们进行断点调试。

SpringBoot自动配置原理详解

太神奇了,咱们经过 run 拿到了 Test 的 Bean 实例

SpringBoot自动配置原理详解

假如咱们试图获取一个没有运用 Bean 办法注册的类,就会抛出异常。

SpringBoot自动配置原理详解

这个 run 其实 能够简略地了解为 Spring 的 IOC 容器,SpringBoot 发动时会主动帮咱们装备程序运转需求的运用的 Bean 目标放到 IOC 容器中,咱们在其他类需求运用时只需求运用 @Autowire 或许 @Resource 注解进行依靠注入即可。

Spring Boot 主动装备是 Spring Boot 结构的一项中心特性,它能够依据运用程序的依靠联系和装备信息,
主动装备运用程序所需的 Spring Bean。Spring Boot 主动装备是经过条件化装备完成的,
这意味着只要在特定条件下才会运用这些装备。这些条件能够是运用程序的依靠联系、装备值、环境变量等等。
Spring Boot 供给了许多 Starter 包,这些 Starter 包为运用程序添加了一组默许的依靠联系和装备信息,
以便运用程序能够正常运转。例如,Spring Boot Starter Web 包为运用程序添加了 
Spring MVC、Tomcat 等 Web 相关的依靠联系和装备信息。当运用程序添加了 Spring Boot Starter Web 包时,
Spring Boot 会主动装备运用程序的 Web 相关装备。
主动安装是指Spring Boot的依靠注入机制,它会依据需求主动为运用程序中的Bean注入依靠联系。
例如,当一个类需求运用JdbcTemplate来拜访数据库时,Spring Boot会主动将JdbcTemplate目标示入到这个类中,
而不需求程序员手动编写任何装备代码。

如下图,咱们在项目中要运用 Redis ,只需求在 pom 文件中引入相关依靠,然后在 application.yml 文件中装备相关连接参数,之后的累活就悉数交给 SpringBoot 主动进行装备,咱们运用时只需求运用 @Autowire 或许 @Resource 注解进行依靠注入即可

SpringBoot自动配置原理详解

经过上面的解说,咱们对 SpringBoot 主动装备有了一个大约的知道——咱们需求什么,就在 pom 文件中引入相关依靠,然后在 application.yml 文件中装备相关装备信息,然后 SpringBoot 会帮咱们把需求的 Bean 都主动装备到 Spring IOC 容器中,之后咱们运用时只需求经过依靠注入机制将需求的 Bean 目标示入即可。

到现在,咱们应该知道了 SpringBoot 主动装备是什么,可是这样咱们只是知其所以然,所以咱们还需求继续了解它的底层完成。

3 源码解读

咱们把目光来到今日的主角,@SpringBootApplication 注解。

SpringBoot自动配置原理详解
咱们进入这个注解,好嘛,它的头上怎样顶着这么多注解,不过真正重要的只要三个注解,咱们接下来会一一介绍。
SpringBoot自动配置原理详解

3.1 @SpringBootConfiguration

点进@SpringBootConfiguration 注解,能够发现其间心注解为@Configuration注解:

SpringBoot自动配置原理详解
@Configuration注解是Spring结构的注解之一,用于符号装备类。

在Spring Boot中,运用@Configuration注解能够将该类作为装备类,然后使该类中的Bean能够被Spring IoC容器办理和运用。

在装备类中,咱们能够运用别的两个注解@Bean和@Scope来界说Bean,其间@Bean注解用于界说Bean目标,而@Scope注解用来指定Bean目标的效果域。

除此之外,在装备类中,咱们还能够界说一些常量,并运用@Value注解来注入运用程序的特点。

举例来说,一个简略的装备类能够被界说如下:

@Configuration
public class MyConfiguration {
    @Value("${myapp.something}")
    private String something;
    @Bean
    public MyBean myBean() {
        return new MyBean(something);
    }
}

在上述代码中,咱们运用@Configuration注解来符号MyConfiguration类为装备类。运用@Value注解来注入myapp.something特点到该类中的something变量中。

同时,咱们运用@Bean注解来界说一个名为myBean的Bean目标,并在Bean办法中回来一个新创立的MyBean目标,将something参数作为其结构函数的参数进行传递。

@Configuration 注解还能够与 @Import 注解一同运用,@Import 注解用于导入其他的装备类,然后组合多个装备类,构成一个完好的运用程序装备。这样,运用程序能够分而治之,将装备信息分散到不同的装备类中,然后使得装备愈加灵敏和可维护。

总的来说,@Configuration注解能够将一个类界说为Spring Boot运用程序中的装备类,然后使该类中的Bean目标能够被Spring IoC容器进行主动办理和安装。这让运用开发者能够愈加专注于运用逻辑的完成,而不必花费精力在繁琐的装备上。

所以@SpringBootConfiguration 注解本质上便是一个@Configuration注解,用来标示某个类为 JavaConfig 装备类,有了这个注解就能够在 SpringBoot 发动类中运用“`@Bean“标签装备类了,如下图所示。

SpringBoot自动配置原理详解

3.2 @ComponentScan

@ComponentScan 是 Spring Framework 中的一个注解,它用于指定 Spring 容器需求扫描和办理的组件。组件是 Spring 中的一个抽象概念,它包含了 Spring Bean、Controller、Service、Repository 等等。经过 @ComponentScan 注解,能够让 Spring 容器主动扫描和办理这些组件,然后简化运用程序的装备和办理。

@ComponentScan 注解有多个参数,能够用于指定要扫描的组件的方位、扫除不需求扫描的组件、指定要扫除扫描的组件等等。

默许情况下,Spring Boot会主动扫描主运用程序下的一切组件(@Configuration, @Controller, @Service, @Repository等),可是假如你将组件放在其他包下,那么就需求显式地装备扫描目录。

举个比方,假设咱们有以下目录结构:

com
|-- myapp
|   |-- Application.java
|   +-- config
|       +-- MyConfiguration.java
+-- other
    +-- MyComponent.java

能够在主运用程序中添加@ComponentScan注解,来指定Spring应该扫描的包方位:

@SpringBootApplication
@ComponentScan(basePackages = { "com.myapp", "com.other" })
public class Application {
    // ...
}

在上述代码中,咱们运用@ComponentScan注解,并指定两个基本包途径com.myapp和com.other以进行扫描。这两个途径下的组件都会被主动扫描到并加载入Spring IoC容器中。

除了basePackages参数以外,@ComponentScan注解还有一些其他可选参数:

除了basePackages参数以外,@ComponentScan注解还有一些其他可选参数:

  1. basePackageClasses:能够运用一个或多个类作为基础包来指定要扫描的根目录。比方:@ComponentScan(basePackageClasses = {MyComponent.class, MyService.class})。

  2. excludeFilters:能够指定过滤器来扫除带有某些注解或完成某些接口的组件。

  3. includeFilters:能够指定过滤器来仅包含带有某些注解或完成某些接口的组件,可能的值有@Component, @Repository, @Service, @Controller等。

运用这些参数,能够愈加精细的操控扫描范围。

3.3 @EnableAutoConfiguration 注解

这是今日的主角中的主角,主动装备完成的中心注解。

点进这个注解能够发现,如下图所示。

SpringBoot自动配置原理详解

咱们重点来看 @Import(AutoConfigurationImportSelector.class)这个注解。

@Import 注解是 它用于将一个或多个类导入到 Spring 容器中,以便于在运用程序中运用。经过 @Import 注解,咱们能够将一些非 Spring 办理的类实例化并注册到 Spring 容器中,或许将一些 Spring 办理的装备类导入到当前装备类中,以便于在运用程序中进行一致的装备和办理。

@Import是Spring Framework 中的一个注解,用于在装备类中导入其他装备类或许一般的Java类。

经过@Impor注解,它用于将一个或多个类导入到 Spring 容器中,以便于在运用程序中运用。经过 @Import 注解,咱们能够将一些非 Spring 办理的类实例化并注册到 Spring 容器中,或许将一些 Spring 办理的装备类导入到当前装备类中,以便于在运用程序中进行一致的装备和办理。

说白了在这儿@Import注解的效果便是将 AutoConfigurationImportSelector 这个类导入当前类,这个类便是完成主动装备的中心。

咱们继续进入到 AutoConfigurationImportSelector 类:

SpringBoot自动配置原理详解

SpringBoot自动配置原理详解
SpringBoot自动配置原理详解
最终,咱们发现, AutoConfigurationImportSelector 实践上是完成了 ImportSelector 接口,这个接口只要两个办法,其间咱们需求重点重视 selectImports() 办法。

ImportSelector 接口是 Spring Framework 中的一个接口,它能够用于在 Spring 容器发动时动态地导入一些类到 Spring 容器中。经过完成 ImportSelector 接口,并重写其间的 selectImports 办法,咱们能够自界说逻辑来确认需求导入的类,然后完成愈加灵敏的装备和办理。

selectImports 办法是 ImportSelector 接口中的一个办法,用于回来需求导入的类的全限制类名数组。在 Spring 容器发动时,Spring 会扫描一切完成了 ImportSelector 接口的类,并调用其间的 selectImports 办法来确认需求导入的类。在 selectImports 办法中,咱们能够自界说逻辑来确认需求导入的类,例如依据某些条件来动态地确认需求导入的类。

好嘛,搞了半天,关键点在这儿,经过 selectImports 办法,咱们就能够得到需求主动装备的类的全限制类名数组,那咱们来看一下这个办法。

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

SpringBoot自动配置原理详解
既然咱们需求主动装备的类的全限制类名数组,那么这个办法必定经过某个办法获取到这个数组,咱们看一下这个办法getAutoConfigurationEntry(annotationMetadata),单看这个名字它的嫌疑就非常大。

getAutoConfigurationEntry 办法能够用于获取主动装备类的元数据,以便于剖析和调试主动装备机制。
它承受一个 AnnotationMetadata 目标作为参数,该目标表明运用了 @EnableAutoConfiguration 注解的装备类的元数据。
经过调用该办法,咱们能够获取到一切现已装备的主动装备类的全限制类名,以及这些主动装备类的条件注解和优先级信息等。

咱们继续进入到 getAutoConfigurationEntry() 办法:

SpringBoot自动配置原理详解
说实话这个办法我现在看仍是感觉眼花缭乱,哈哈,不过不影响咱们剖析,咱们先看办法回来值,回来值是一个 AutoConfigurationEntry 目标,再看看 return 句子:

return new AutoConfigurationEntry(configurations, exclusions);

果然是经过结构函数创立一个 AutoConfigurationEntry 目标并回来,咱们再看看它的结构参数:

configurations, exclusions

再结合咱们之前的剖析,这个办法的效果是回来主动装备类的元数据,不难推断出 configurations 便是咱们需求的主动装备类的元数据,那exclusions 参数呢,这个从名字上来看,它应该是需求扫除的类的元数据。

相似上面 @ComponentScan注解 中的 excludeFilter 参数,能够指定过滤器来扫除带有某些注解或完成某些接口的组件。

那咱们现在要做的便是剖析 configurations 是怎样来的:

List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

咱们继续进入到 getCandidateConfigurations() 办法:

SpringBoot自动配置原理详解

这个办法的组成仍是非常简略的,它只调用了 SpringFactoriesLoader 的静态办法 loadFactoryNames(),还有便是一个断语。

Java 断语是一种调试东西,它用于在程序运转时查看一个条件是否为 true。
能够运用 assert 关键字来编写断句子子,假如条件为 false,则会抛出 AssertionError 异常。
getCandidateConfigurations 办法是 Spring Boot 中的一个办法,它用于获取一切候选的主动装备类。
在 Spring Boot 运用程序中,主动装备是一种约定俗成的机制,它能够依据运用程序的依靠和装备来主动装备 Spring 运用程序上下文。
Spring Boot 会在 classpath 下扫描 META-INF/spring.factories 文件,该文件中界说了一些主动装备类,
这些主动装备类会在运用程序发动时被主动加载和装备。

咱们先来了解一下 SpringFactoriesLoader :

SpringFactoriesLoader 是 Spring 结构中的一个东西类,用于加载 META-INF/spring.factories 文件中界说的类。
在 Spring Boot 运用程序中,META-INF/spring.factories 文件中界说了一些主动装备类,
这些主动装备类会在运用程序发动时被主动加载和装备。
SpringFactoriesLoader 能够用于加载这些主动装备类,然后完成主动装备机制。

接下来咱们继续进入 loadFactoryNames() 办法:

SpringBoot自动配置原理详解

SpringFactoriesLoader 类中的 loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) 办法用于加载指定类型的工厂完成类。该办法承受两个参数:

  1. factoryType: 要加载的工厂类型,有必要是一个接口或许抽象类。该参数是有必要的,因为 SpringFactoriesLoader 会在 META-INF/spring.factories 文件中查找该工厂类型对应的完成类。

  2. classLoader: 类加载器,用于加载 META-INF/spring.factories 文件中界说的类。假如该参数为 null,则运用当前线程的上下文类加载器。 该办法会回来一个 List 类型的目标,其间包含了一切的候选工厂完成类的全限制类名。在加载工厂完成类时,SpringFactoriesLoader 会运用反射机制创立实例,并调用工厂办法生成对应的工厂目标。

了解了 loadFactoryNames()办法后,咱们先把目光回到 getCandidateConfigurations() 办法,它在调用 loadFactoryNames()办法时传递了两个参数:

SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())

SpringBoot自动配置原理详解

这两个参数是两个办法:

SpringBoot自动配置原理详解

SpringBoot自动配置原理详解

结合传递的参数进行剖析,这儿 loadFactoryNames() 办法的效果是:

加载一切运用了 @EnableAutoConfiguration 注解的主动装备类的全限制类名,并回来一个 List 类型的目标,其间包含了一切的候选主动装备类的全限制类名。

咱们把目光回到 loadFactoryNames() 办法,不难看出,实践的加载功能运用最终办法回来处调用的 :

loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList())

完成的。

SpringBoot自动配置原理详解

咱们先来看一下 getOrDefault()办法:

getOrDefault(Object key, V defaultValue) 是 Map 接口中的一个办法,
用于获取指定 key 对应的 value。假如该 key 存在,则回来对应的 value;不然,回来 defaultValue。

也便是说 loadSpringFactories(classLoaderToUse) 办法,回来的是一个 Map 类型的数据,而 getOrDefault()办法的 key 为:

String factoryTypeName = factoryType.getName();

SpringBoot自动配置原理详解
显然,这个 factoryTypeName 是 EnableAutoConfiguration。

所以 loadSpringFactories(classLoaderToUse) 办法会回来的是一个 Map 类型的数据,并且结合 getOrDefault()传递的参数 key 可知,这个 Map 数据的 key 应该是 EnableAutoConfiguration,而 value 是一个 List<String>调集,所以这个 Map 类型的数据为 Map<String, List<String>>

原来 SpringBoot 经过 loadSpringFactories 办法获得了 Map<String, List<String>>数据结构的数据然后再经过 getOrDefault 办法将其转化成 List<String>数据结构。

剖析到这儿,咱们再来看一下 loadSpringFactories(ClassLoader classLoader) 办法:

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 {
			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;
	}

这个办法代码许多,咱们一点一点的剖析。

SpringBoot自动配置原理详解
办法开始,先尝试从缓存中获取 Map<String, List<String>>类型的回来值 result,假如缓存命中就直接回来,假如缓存中没有,就继续往下执行。

SpringBoot自动配置原理详解
到了这儿,咱们现已来到了这个办法的中心,简略剖析一下这段代码的效果:

这段代码的效果是加载指定方位的资源并解析其间的特点,获取工厂类型和对应的完成类名,然后将它们存储在一个 Map 中。
详细来说,这段代码的完成进程如下:
1.获取指定方位 FACTORIES_RESOURCE_LOCATION 的一切资源 URL。
2.遍历一切获取到的 URL,对每一个 URL 进行如下操作:
	a.将 URL 封装成一个 UrlResource 目标,用于拜访该 URL 资源。
	b.运用 PropertiesLoaderUtils 东西类加载 UrlResource 目标中的特点,获取工厂类型和对应的完成类名。
	c.遍历工厂类型对应的完成类名数组,将每个完成类名添加到一个 Map 目标中,以工厂类型为键,以完成类名列表为值。
	  这儿运用了 computeIfAbsent 办法,假如该工厂类型在 Map 目标中不存在,则会创立一个新的键值对,
	  不然会将完成类名添加到该工厂类型对应的完成类名列表中。
3.回来包含工厂类型和对应的完成类名的 Map 目标。
该段代码通常用于 Spring Boot 运用程序中的主动装备,主要意图是在发动时主动加载并装备一些主动装备类,
以减少手动装备的工作量。在 Spring Boot 运用程序中,这段代码通常会在 AutoConfigurationImportSelector 类中被调用,
用于加载并解析 META-INF/spring.factories 文件中界说的主动装备类。

在这个办法中 FACTORIES_RESOURCE_LOCATION :

SpringBoot自动配置原理详解

搞了这么久,总算破案了,这个 loadSpringFactories 便是依据装备信息的 url 加载装备文件的内容,接下来咱们进行断点调试,验证咱们的猜测。

SpringBoot自动配置原理详解

看,咱们从 result 中找到了 key 为 EnableAutoConfiguration ,value 为 List<String> 的 Map 类型的数据。

SpringBoot自动配置原理详解
再看看 spring.factories 文件中的内容,进一步印证了咱们的估测。
SpringBoot自动配置原理详解

最终,咱们再次将目光会到 loadSpringFactories() 办法,这个办法先获取 loadSpringFactories() 办法回来的 Map<String, List<String>>数据结构的数据,这儿封装了从 spring.factories 文件中获取的类的全限制名信息。

然后再经过 getOrDefault(factoryTypeName, Collections.emptyList()) 办法获取 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration
List<String>类型的数据。这个 list 调集里边便是 SpringBoot 主动装备的类的全限制名信息。

SpringBoot自动配置原理详解

4 SpringFactories 机制

SpringFactories 机制是 Spring 结构供给的一种扩展机制,用于在运用程序发动时主动加载并装备一些扩展类。详细来说,该机制经过在类途径下的 META-INF/spring.factories 文件中界说一些扩展类的全限制类名,然后在运用程序发动时主动扫描该文件,并加载其间的扩展类。 SpringFactories 机制的完成进程如下:

  1. 在类途径下的 META-INF/spring.factories 文件中界说一些扩展类的全限制类名。

  2. 在运用程序发动时,运用 ClassLoader 加载 META-INF/spring.factories 文件,并解析其间界说的扩展类名。

  3. 依据扩展类名运用反射机制动态创立扩展类的实例,并将其注册到相应的容器中。例如,在 Spring Boot 运用程序中,主动装备类会被注册到 Spring 容器中,并在运用程序发动时主动装备。

SpringFactories 机制的长处是能够极大地下降运用程序的装备难度,提高开发功率。在 Spring Boot 运用程序中,该机制被广泛运用于主动装备、自界说 Starter、插件等领域。

总结

没错,咱们上面剖析的 loadSpringFactories() 办法便是依据 SpringFactories 机制完成的。

那假如面试官问,说说你对 SpringBoot 主动装备的了解,咱们该怎样答复?

SpringBoot 主动装备便是依据SpringFactories 机制获取对应依靠META-INF目录下的 spring.factories 文件中的需求主动装备的类的全限制名信息,然后依据这些信息将咱们需求的运用的 Bean 目标放到 IOC 容器中,当咱们需求运用时,经过依靠注入机制直接注入运用即可。当然假如再追问详细的完成细节,能够依据咱们的剖析流程讲讲详细的代码完成。

以上文字都是我自己依据自己的了解写的,所以不免有过错的当地,有任何问题,或许文章有任何过错,请在评论区@我。