SpringBoot自动装配源码剖析

SpringBoot自动装配源码剖析

前言

我们现在最常使用的框架肯定非SpringBoot莫属了,相较于Spring框架我们都知道其简化了很多配置,使其使用起来更加便捷,不需要程序员过度的关心配置。那么你知道SpringBoot是如何完成这样的工作的吗?本文就带大家来具体剖析一下工作原理。

首先我们要先弄清楚 什么是SpringBoot的自动装配,它是干什么的,又完成了什么样的工作?

用大白话来说:其实就是当我们引入starter之后可以自动的将这个组件注入到Spring容器中供我们使用

流程图

为了方便大家理解,首先我们先通过一个流程图来简要的叙述一下SpringBoot自动装配的流程,接下来我们再从代码层面去具体分析一下它的工作原理。

未命名文件 (1).png

源码剖析

@SpringBootApplication

image.png

这里就是我们SpringBoot的入口了,我们通过@SpringBootApplication 来标明我们的程序入口,以及完成自动装配的操作,我们点进去看一眼

image.png

很明显,@SpringBootApplication 是一个复合注解,它其中包含了许多的注解,但是别被这么多的注解吓得腿软了伙计,这里主要的注解其实只有三个:@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan ,接下来我们逐一来讲一下它们都完成的什么事

@SpringBootConfiguration

image.png

我们点进去这个注解可以看到,这个注解也是一个复合注解,但是最主要的注解是这个@Configuration ,这个注解我们再熟悉不过了,它的作用是将使用该注解的类标明该类为配置类,并交给容器来管理,那么去除多余的注解,这个注解的主要作用就是:将被修饰的类交给容器来管理。

@ComponentScan

image.png

这个注解我们不需要点到注解内部,这个注解在我们工作中使用的可以说很频繁了,它的作用就是扫描包,并将包下的类及子类均交给容器来管理。其中有一些属性我们或许没用过,别担心老伙计,我这就给你讲一下这些属性都是干什么的。

其中只使用了一个属性:excludeFilters ,这个属性的含义为排除掉满足@Filter 条件的类,在@Filter 注解中,type 在该处的值为一个枚举,Custom表明该过滤规则为自定义规则,classes 指明了该过滤规则的类

综上所述,该注解的含义为将满足Filter除外所有所需的类加载到容器中。

@EnableAutoConfiguration

image.png

我们点到该注解内部后发现,该注解也是一个复合注解,其主要的注解为:@AutoConfigurationPackage@Import 两个注解,其中 @Import 注解为SpringBoot自动装配的 核心注解,我们一个一个来分析。

@AutoConfigurationPackage

image.png

我们进入到该注解的内部可以发现,里面也有一个 @Import 注解,同时该注解也为 @AutoConfigurationPackage 的核心注解。

在SpringBoot中,该注解会向Spring容器内部注册一个类型为 AutoConfigurationPackages.BasePackages 的Bean,这个Bean里保存了SpringBoot启动类的路径,后续会配合前面的@ComponentScan 注解来扫描并注入由@Component@Controller@Service@Repository@Configuration 注解所修饰的类。

我们再来看一些在 @Import 注解中声明的类 AutoConfigurationPackages.Registrar

image.png

进入到这个类内部可以发现,Registrar为AutoConfigurationPackages的一个内部类,其内部有两个方法:registerBeanDefinitionsdetermineImports。

其中比较重要的是 registerBeanDefinitions 方法,在Spring容器启动的过程中也会调用这个方法。可以看到该方法调用了 AutoConfigurationPackages.register() 方法,我们可以点到该方法内部来分析它都做了什么。

image.png

该方法内部调用了 BeanDefinitionRegistry.registerBeanDefinition() 方法,这个方法会将包路径封装成一个 BasePackagesBeanDefinition,然后将其注册到注册表中。

综上所述,@AutoConfigurationPackage 完成的就是 将包路径注册到注册表中供其他地方使用。

@Import({AutoConfigurationImportSelector.class})

这个注解的重点在于后面的 AutoConfigurationImportSelector 这个类,那么我们进入到这个类内部看一下都有什么。

image.png

我们可以看到这个类实现了很多类,其中比较重要的是实现的第一个类 DeferredImportSelector,我们再进入这个类内部进行分析。

image.png

可以看到该类继承了 ImportSelector 这个类,那我们就继续往里面走。

image.png

我们可以清晰的看到这个接口里只有两个方法:selectImports 方法和 getExclusionFilter 方法,其中比较重要的是 selectImports 这个方法,那我们就去其实现类下面看看这个方法究竟完成了什么事。

或许有小伙伴会问:这么多实现类每个实现的都不一样,我怎么知道看哪个。你这就心急了不是,虽然它有很多实现类,但是我们看名字可以看到有一个 AutoConfigurationImportSelector,顾名思义,这个开头就是自动装配,嘿!就是它没错了,我们快去看看里面完成了啥。

image.png

在这个方法里我们可以看到它调用了 getAutoConfigurationEntry() 这个方法,那我们就去看看这个方法里完成了什么样的事情。

image.png

我们来逐一分析一下它都干了什么事儿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 1.判断是否启用了自动装配,默认为true,在yml中配置项为spring.boot.enableautoconfiguration
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
// 2.获取注解中的排除项
// 获取注解属性
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 3.获取所有需要自动装配的配置类
// 读取所有预配置类,即读取spring.factories
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 去除所有重复的配置类
configurations = this.removeDuplicates(configurations);
// 获取排除类
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 校验排除的类
this.checkExcludedClasses(configurations, exclusions);
// 去除所有排除类
configurations.removeAll(exclusions);
// 4.根据排除规则进行过滤
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
// 返回自动装配的对象
return new AutoConfigurationEntry(configurations, exclusions);
}
}

分析getAutoConfigurationEntry方法

isEnabled

image.png

在这个方法里我们可以看到,其 defaultValue 值为 true,也就是默认为 true,同时我们也可以在 ymlproperties 中设置该项的值

getAttributes

image.png

可以看到这个方法是想要获取注解的属性

getCandidateConfigurations

image.png

其中调用了 loadFactoryNames 的方法,我们进入到这个方法内部来分析。

image.png

这个方法最后调用了 loadSpringFactories 方法,我们再进到这个方法进一步去分析。

image.png

在这里我们清晰的看到在这里去读取了 META-INF 下的 spring.factories 文件,然后将所有读取到的预配置类返回,spring.factories文件内容如下:

image.png

其内容是以key-value的形式进行存储的,用于存储预加载类的全限定名。

removeDuplicates

image.png

这个方法就很好理解了,就是去重再返回

getExclusions

image.png

这里就是获取注解中声明的排除项

checkExcludedClasses

image.png

这里做的工作就是去二次校验是否有遗漏的配置类没被排除中,如果有则补上,再配合 removeAll 方法将被排除的类从预加载类中删除

最后几步就是根据排除规则去过滤需要排除掉的类,最后返回一个自动装配的对象,最终完成自动装配的工作。

注意事项

Q:所有的自动装配的类都会被加载到这个地方吗,有没有其他的配置?

A:并不是所有的配置都会被加载到此处,而是 所有的starter下的 META-INF/spring.factories 都会被加载

Q:spring.factories中有众多的配置类,随着后续依赖的增加配置也会越来越多,那么每次启动都要加载这些类吗?

A:并不是每次都会加载,因为在此处还经历了一个筛选的过程,即:@ConditionalOnXXX ,只有满足了该注解中的所有条件,最终才会被加载,如下:

  • @ConditionalOnBean:当容器里有指定 Bean 的条件下
  • @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
  • @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
  • @ConditionalOnClass:当类路径下有指定类的条件下
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下
  • @ConditionalOnProperty:指定的属性是否有指定的值
  • @ConditionalOnResource:类路径是否有指定的值
  • @ConditionalOnExpression:基于 SpEL 表达式作为判断条件
  • @ConditionalOnJava:基于 Java 版本作为判断条件
  • @ConditionalOnJndi :在 JNDI 存在的条件下差在指定的位置
  • @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
  • @ConditionalOnWebApplication:当前项目是 Web 项 目的条件下

总结

SpringBoot的自动装配就是 将我们引入的starter中的组件自动加载到Spring容器进行管理的过程。

SpringBoot自动装配的过程是:通过 @EnableAutoConfiguration 注解来开启SpringBoot的自动装配,然后将我们要自动装配的类加载到 spring.factories 中,最后通过 SpringFactoriesLoader 来加载 spring.factories 中的配置类来实现自动装配,并通过 @ConditionalOnXXX 注解来按需加载。